首先先构建SpringBoot项目,编写对应的pom文件
1、pom.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.axl.shiro</groupId>
<artifactId>shiro-springboot-shiro</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>shiro-springboot-shiro</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!--jstl标签版本 -->
<jstl.version>1.2</jstl.version>
<!--servlet版本-->
<servlet.version>4.0.1</servlet.version>
<!--jdk版本-->
<java.version>1.8</java.version>
<!--mybatis版本-->
<mybatis.version>2.0.0</mybatis.version>
<!--分页工具版本-->
<pagehelper.version>4.1.1</pagehelper.version>
<!--druid版本-->
<druid.version>1.1.20</druid.version>
<!--spring-boot版本-->
<spring.boot.version>2.1.6.RELEASE</spring.boot.version>
<!--系统版本-->
<itheima.version>1.0-SNAPSHOT</itheima.version>
<!--mysql-->
<mysql-connector-java.version>5.1.44</mysql-connector-java.version>
<!--lombok-->
<lombok.version>1.18.8</lombok.version>
<!--fastjson-->
<fastjson.version>1.2.47</fastjson.version>
<!--shiro-->
<shiro.version>1.3.2</shiro.version>
<!--junit-->
<junit.version>4.12</junit.version>
<!--jackson版本-->
<jackson.version>2.9.0</jackson.version>
<!--tomcat-->
<tomcat.embed.version>9.0.21</tomcat.embed.version>
<!--lang3-->
<commons.lang3.version>3.8.1</commons.lang3.version>
<!-- slf4j2日志版本 -->
<log4j.version>2.8.2</log4j.version>
</properties>
<dependencies>
<!--jstl-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
<!--servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.lang3.version}</version>
</dependency>
<!--JTA事务-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!--springboot组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>${spring.boot.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>${tomcat.embed.version}</version>
</dependency>
<!-- SECURITY begin -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- SECURITY end -->
<!--druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!--springboot关于druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!--mysql数据源-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pagehelper.version}</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!--jackson依赖包-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!-- log4j2驱动依赖 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
<version>${log4j.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- compiler插件, 设定JDK版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>8</source>
<target>8</target>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
<!-- 声明打包时,不需要web.xml -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<!-- springboot打包插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.6.RELEASE</version>
</plugin>
<!-- mybatis-generator-maven-plugin -->
</plugins>
</build>
</project>
由于真实项目中的密码或者是用户信息都是保存在数据库中,所以我们需要自定义Realm,继承AuthorizingRealm,通过重写AuthorizingRealm中的方法对数据库进行操作
2、自定义Realm
(1)ShiroDbRealmImpl继承ShiroDbRealm向上继承AuthorizingRealm,ShiroDbRealmImpl实例化时会创建密码匹配器HashedCredentialsMatcher实例,HashedCredentialsMatcher指定hash次数与方式,交于AuthenticatingRealm
(2)调用login方法后,最终调用doGetAuthenticationInfo(AuthenticationToken authcToken)方法,拿到SimpleToken的对象,调用Userdao的查找用户方法,把ShiroUser对象、密码和salt交于SimpleAuthenticationInfo去认证
(3)访问需要鉴权时,调用doGetAuthorizationInfo(PrincipalCollection principals)方法,然后调用Userdao的授权验证
【2.1】SimpleToken(自定义Token)
package com.axl.shiro.core.base;
import org.apache.shiro.authc.UsernamePasswordToken;
/**
* @Description 自定义tooken
*/
public class SimpleToken extends UsernamePasswordToken {
/** serialVersionUID */
private static final long serialVersionUID = -4849823851197352099L;
private String tokenType;
private String quickPassword;
/**
* Constructor for SimpleToken
* @param tokenType
*/
public SimpleToken(String tokenType, String username,String password) {
super(username,password);
this.tokenType = tokenType;
}
public SimpleToken(String tokenType, String username,String password,String quickPassword) {
super(username,password);
this.tokenType = tokenType;
this.quickPassword = quickPassword;
}
public String getTokenType() {
return tokenType;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
public String getQuickPassword() {
return quickPassword;
}
public void setQuickPassword(String quickPassword) {
this.quickPassword = quickPassword;
}
}
【2.2】ShiroDbRealm
package com.axl.shiro.core;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import javax.annotation.PostConstruct;
/**
*
* @Description shiro自定义realm
*/
public abstract class ShiroDbRealm extends AuthorizingRealm {
/**
* @Description 认证
* @param authcToken token对象
* @return
*/
public abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) ;
/**
* @Description 鉴权
* @param principals 令牌
* @return
*/
public abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);
/**
* @Description 密码匹配器
*/
@PostConstruct
public abstract void initCredentialsMatcher() ;
}
【2.3】ShiroDbRealmImpl
package com.axl.shiro.core.impl;
import com.axl.shiro.constant.SuperConstant;
import com.axl.shiro.core.base.ShiroUser;
import com.axl.shiro.core.base.SimpleToken;
import com.axl.shiro.core.ShiroDbRealm;
import com.axl.shiro.core.bridge.Userdao;
import com.axl.shiro.pojo.User;
import com.axl.shiro.utils.DigestsUtil;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @Description:自定义shiro的实现
*/
public class ShiroDbRealmImpl extends ShiroDbRealm {
@Autowired
private Userdao userdao;
/**
* @Description 认证方法
* @param authcToken 校验传入令牌
* @return AuthenticationInfo
*/
@Override
public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) {
SimpleToken token = (SimpleToken)authcToken;
//根据用户名查找数据库中的用户
User user = userdao.findUserByLoginName(token.getUsername());
if(user == null){
throw new UnknownAccountException("账号不存在");
}
//根据用户id查找用户拥有的资源ids集合,并设置在用户实体类中
user.setResourceIds(userdao.findResourcesIdsList(user.getId()));
//获取用户密码加密的盐
String salt = user.getSalt();
//获取用户加密后的密码
String password = user.getPassWord();
//通过密码和盐去判断用户密码是否正确,正确则通过认证
return new SimpleAuthenticationInfo(user, password, ByteSource.Util.bytes(salt), getName());
}
/**
* @Description 授权方法
* @param principals SimpleAuthenticationInfo对象第一个参数
* @return
*/
@Override
public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
User user = principals.getPrimaryPrincipal();
//查询用户对应的角色标识
List<String> roleList = userdao.findRoleList(user.getId());
//查询用户对于的资源标识
List<String> resourcesList = userdao.findResourcesList(user.getId());
//构建鉴权信息对象
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//添加角色集合
simpleAuthorizationInfo.addRoles(roleList);
//添加资源集合
simpleAuthorizationInfo.addStringPermissions(resourcesList);
return simpleAuthorizationInfo;
}
/**
* @Description 加密方式
*/
@Override
public void initCredentialsMatcher() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(SuperConstant.HASH_ALGORITHM);
matcher.setHashIterations(SuperConstant.HASH_INTERATIONS);
setCredentialsMatcher(matcher);
}
}
3、ShiroConfig配置(重点)
(1)创建SimpleCookie,访问项目时,会在客户端中cookie中存放ShiroSession的Id
(2)创建DefaultWebSessionManager会话管理器定义cookie机制、定时刷新、全局会话超时时间然后交
于DefaultWebSecurityManager权限管理器管理
(3)创建自定义ShiroDbRealm实现,用于权限认证、授权、加密方式的管理,同时从数据库中取得相关的
角色、资源、用户的信息,然后交于DefaultWebSecurityManager权限管理器管理
(4)创建DefaultWebSecurityManager权限管理器用于管理DefaultWebSessionManager会话管理器、ShiroDbRealm
(5)创建lifecycleBeanPostProcessor和DefaultAdvisorAutoProxyCreator相互配合事项注解的权限鉴权
(6)创建ShiroFilterFactoryBean的shiro过滤器指定权限管理器、同时启动连接链及登录URL、未登录的URL的跳转
【3.1】ShiroConfig代码
package com.axl.shiro.config;
import com.axl.shiro.core.ShiroDbRealm;
import com.axl.shiro.core.impl.ShiroDbRealmImpl;
import com.axl.shiro.properties.PropertiesUtil;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @Description:权限配置类
*/
@Configuration
@ComponentScan(basePackages = "com.axl.shiro.core")
@Log4j2
public class ShiroConfig {
/**
* @Description 创建cookie对象
*/
@Bean(name="sessionIdCookie")
public SimpleCookie simpleCookie(){
SimpleCookie simpleCookie = new SimpleCookie();
simpleCookie.setName("ShiroSession");
return simpleCookie;
}
/**
* @Description 会话管理器
*/
@Bean(name="sessionManager")
public DefaultWebSessionManager shiroSessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
//关闭会话更新
sessionManager.setSessionValidationSchedulerEnabled(false);
//生效cookie
sessionManager.setSessionIdCookieEnabled(true);
//指定cookie的生成策略
sessionManager.setSessionIdCookie(simpleCookie());
//指定全局会话超时时间
sessionManager.setGlobalSessionTimeout(3600000);
return sessionManager;
}
/**
* @Description 自定义RealmImpl
*/
@Bean(name="shiroDbRealm")
public ShiroDbRealm shiroDbRealm(){
return new ShiroDbRealmImpl();
}
/**
* @Description 权限管理器
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//添加自定义Realm
securityManager.setRealm(shiroDbRealm());
securityManager.setSessionManager(shiroSessionManager());
return securityManager;
}
/**
* @Description AOP式方法级权限检查
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* @Description 配合DefaultAdvisorAutoProxyCreator事项注解权限校验
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(defaultWebSecurityManager());
return new AuthorizationAttributeSourceAdvisor();
}
/**
* @Description 过滤器链
*/
private Map<String, String> filterChainDefinition(){
List<Object> list = PropertiesUtil.propertiesShiro.getKeyList();
Map<String, String> map = new LinkedHashMap<>();
for (Object object : list) {
String key = object.toString();
String value = PropertiesUtil.getShiroValue(key);
log.info("读取防止盗链控制:---key{},---value:{}",key,value);
map.put(key, value);
}
return map;
}
/**
* @Description Shiro过滤器
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(defaultWebSecurityManager());
shiroFilter.setFilterChainDefinitionMap(filterChainDefinition());
shiroFilter.setLoginUrl("/login");
shiroFilter.setUnauthorizedUrl("/login");
return shiroFilter;
}
}
4、根据需求编写properties
#静态资源不过滤
/static/**=anon
#登录链接不过滤
/login/**=anon
#其他链接是需要登录的
/**=authc
到这里,我们的SpringBoot就将Shiro整合完毕了
下面再补充一些高级的部分
5、自定义过滤器
实现只要有其中一个角色,则可访问对应路径的过滤器
1、首先我们需要继承AuthorizationFilte
2、然后我们需要去配置ShiroConfig
3、在authentication.properties中使用即可
【5.1】RolesOrAuthorizationFilter
package com.axl.shiro.filter;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.util.Set;
/**
* @Description:mappedValue:访问该资源需要的角色列表
*/
public class RolesOrAuthorizationFilter extends AuthorizationFilter {
@SuppressWarnings({"unchecked"})
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue;
if (rolesArray == null || rolesArray.length == 0) {
//no roles specified, so nothing to check - allow access.
return true;
}
Set<String> roles = CollectionUtils.asSet(rolesArray);
//循环roles判断只要有角色则返回true
for (String role : roles) {
//如果当前用户拥有该角色,返回true并放行
if(subject.hasRole(role)){
return true;
}
}
return false;
}
}
【5.2】编辑ShiroConfig
/**
* @Description 自定义过滤器定义
*/
private Map<String, Filter> filters() {
Map<String, Filter> map = new HashMap<String, Filter>();
map.put("role-or", new RolesOrAuthorizationFilter());
return map;
}
/**
* @Description Shiro过滤器
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(defaultWebSecurityManager());
//使自定义过滤器生效
shiroFilter.setFilters(filters());
shiroFilter.setFilterChainDefinitionMap(filterChainDefinition());
shiroFilter.setLoginUrl("/login");
shiroFilter.setUnauthorizedUrl("/login");
return shiroFilter;
}
【5.3】在authentication.properties中使用
#静态资源不过滤
/static/**=anon
#登录链接不过滤
/login/**=anon
#访问/resource/**需要有admin的角色
/resource/**=role-or[admin]
#其他链接是需要登录的
/**=authc
6、注解方式鉴权
【6.1】注解介绍
以下为常用注解
| 注解 | 说明 |
|---|---|
| @RequiresAuthentication | 表明当前用户需是经过认证的用户 |
| @ RequiresGuest | 表明该用户需为”guest”用户 |
| @RequiresPermissions | 当前用户需拥有指定权限 |
| @RequiresRoles | 当前用户需拥有指定角色 |
| @ RequiresUser | 当前用户需为已认证用户或已记住用户 |
例如RoleAction类中我们添加
/**
*@Description: 跳转到角色的初始化页面
*/
@RequiresRoles(value ={"SuperAdmin","dev"},logical = Logical.OR)
@RequestMapping(value = "listInitialize")
public ModelAndView listInitialize(){
return new ModelAndView("/role/role-listInitialize");
}