权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源,不多不少。权限管理几乎出现在任何系统里面,只要有用户和密码的系统。 很多人常将“用户身份认证”、“密码加密”、“系统管理”等概念与权限管理概念混淆。
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
一句话:Spring Security 的前身是 Acegi Security ,是 Spring 项目组中用来提供安全认证服务的框架
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
一句话:Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能
两者比较:
- Apache Shiro比Spring Security , 前者使用更简单。
- Shiro 功能强大、 简单、灵活, 不跟任何的框架或者容器绑定,可以独立运行。
- Spring Security 对Spring 体系支持比较好,脱离Spring体系则很难开发。
- SpringSecutiry 支持Oauth鉴权 https://spring.io/projects/spring-security-oauth,Shiro需要自己实现。
- …等等
Authentication
,身份证认证,一般就是登录
Authorization
,给用户分配角色或者访问某些资源的权限
Session Management
, 用户的会话管理员,多数情况下是web session
Cryptography
, 数据加解密,比如密码加解密等
我们把用户或者程序称为主体(如用户,第三方服务,cron作业),主体去访问系统或者资源,可以理解为任何与系统交互的“东西”都是Subject。
安全管理器,Subject的认证和授权都要在安全管理器下进行
认证器,主要负责Subject的认证
数据域,Shiro和安全数据的连接器,好比jdbc连接数据库; 通过realm获取认证授权相关信息
授权器,主要负责Subject的授权, 控制subject拥有的角色或者权限
加解密,Shiro的包含易于使用和理解的数据加解密方法,简化了很多复杂的api
缓存管理器,比如认证或授权信息,通过缓存进行管理,提高性能
Maven3.2 + Jdk8 + Springboot 2.X + IDEA
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--运行时才有-->
<!--runtime -->
</dependency>
<!--阿里巴巴数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<!--Spring整合Shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
//是否有对应的角色
subject.hasRole("root")
//获取subject名
subject.getPrincipal()
//检查是否有对应的角色,无返回值,直接在SecurityManager里面进行判断
subject.checkRole("admin")
//检查是否有对应的角色
subject.hasRole("admin")
//退出登录
subject.logout();
步骤:
创建一个类 CustomRealm ,继承AuthorizingRealm
重写授权方法 doGetAuthorizationInfo
重写认证方法 doGetAuthenticationInfo
方法:
当用户登陆的时候会调用 doGetAuthenticationInfo
进行权限校验的时候会调用 doGetAuthorizationInfo
对象介绍:
UsernamePasswordToken : 对应就是 shiro的token中有Principal和Credential
UsernamePasswordToken -> HostAuthenticationToken -> AuthenticationToken
SimpleAuthorizationInfo:代表用户角色权限信息
SimpleAuthenticationInfo :代表该用户的认证信息
package top.mitday.shiro_combat.config;
import jdk.nashorn.internal.parser.Token;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import top.mitday.shiro_combat.entity.Permission;
import top.mitday.shiro_combat.entity.Role;
import top.mitday.shiro_combat.entity.User;
import top.mitday.shiro_combat.service.UserService;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义realm
*/
public class CustomRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 进行权限校验的时候调用
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("用户授权 doGetAuthorizationInfo");
User newuser = (User)principals.getPrimaryPrincipal();
User user = userService.findAllUserInfoByUsername(newuser.getUsername());
List<String> stringRoleList = new ArrayList<>();
List<String> stringPermissionList = new ArrayList<>();
List<Role> roleList = user.getRoleList();
for (Role role : roleList){
stringRoleList.add(role.getName());
List<Permission> permissionList = role.getPermissionList();
for (Permission permission : permissionList){
if (permission != null){
stringPermissionList.add(permission.getName());
}
}
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRoles(stringRoleList);
simpleAuthorizationInfo.addStringPermissions(stringPermissionList);
return simpleAuthorizationInfo;
}
/**
* 用户登录的时候调用
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("用户登录 doGetAuthenticationInfo");
/**
* 从token中获取中户信息,token代表用户的输入
*/
String username = String.valueOf(token.getPrincipal());
User user = userService.findAllUserInfoByUsername(username);
//获取密码
String pwd = user.getPassword();
if (pwd == null || "".equals(pwd)){
return null; }
return new SimpleAuthenticationInfo(user,user.getPassword(),this.getClass().getName());
}
}
// controller
@PostMapping("/login")
public JsonData Login(@RequestBody UserQuery userQuery, HttpServletRequest request, HttpServletResponse response){
Subject subject = SecurityUtils.getSubject();
Map<String, Object> info = new HashMap<>();
try{
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userQuery.getName(),userQuery.getPwd());
subject.login(usernamePasswordToken);
info.put("msg","登录成功");
info.put("session_id",subject.getSession().getId());
return JsonData.buildSuccess(info);
}catch (Exception e){
e.printStackTrace();
return JsonData.buildError("账号或者密码错误!");
}
}
//核心过滤器类:DefaultFilter, 配置哪个路径对应哪个拦截器进行处理
//需要认证登录才能访问
authc:org.apache.shiro.web.filter.authc.FormAuthenticationFilter
//用户拦截器,表示必须存在用户。
user:org.apache.shiro.web.filter.authc.UserFilter
//匿名拦截器,不需要登录即可访问的资源,匿名用户或游客,一般用于过滤静态资源。
anon:org.apache.shiro.web.filter.authc.AnonymousFilter
//角色授权拦截器,验证用户是或否拥有角色。
//参数可写多个,表示某些角色才能通过,多个参数时写 roles["admin,user"],当有多个参数时必须每个参数都通过才算通过
roles:org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
//权限授权拦截器,验证用户是否拥有权限
//参数可写多个,表示需要某些权限才能通过,多个参数时写 perms["user, admin"],当有多个参数时必须每个参数都通过才算可以
perms:org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
//httpBasic 身份验证拦截器。
authcBasic:org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
//退出拦截器,执行后会直接跳转到shiroFilterFactoryBean.setLoginUrl(); 设置的 url
logout:org.apache.shiro.web.filter.authc.LogoutFilter
//端口拦截器, 可通过的端口。
port:org.apache.shiro.web.filter.authz.PortFilter
//ssl拦截器,只有请求协议是https才能通过。
ssl:org.apache.shiro.web.filter.authz.SslFilter
/admin/video /user /pub
路径通配符支持 ?、、**,注意通配符匹配不 包括目录分隔符“/”
**可以匹配所有,不加可以进行前缀匹配,但多个冒号就需要多个 * 来匹配
// URL权限采取第一次匹配优先的方式
? : 匹配一个字符,如 /user? , 匹配 /user3,但不匹配/user/;
* : 匹配零个或多个字符串,如 /add* ,匹配 /addtest,但不匹配 /user/1
** : 匹配路径中的零个或多个路径,如 /user/** 将匹 配 /user/xxx 或 /user/xxx/yyy
例子
/user/**=filter1
/user/add=filter2
请求 /user/add 命中的是filter1拦截器
/**
* 自定义HashedCredentialsMatcher
* 密码加解密
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// 设置散列算法 这里使用MD5算法
credentialsMatcher.setHashAlgorithmName("md5");
//散列次数,散列2次 相当于md5(md5(xxx))
credentialsMatcher.setHashIterations(2);
return credentialsMatcher;
}
使用ShiroConfig
//需要角色 admin 和 editor两个角色 AND表示两个同时成立
@RequiresRoles(value={
"admin", "editor"}, logical= Logical.AND)
//需要权限 user:add 或 user:del权限其中一个,OR是或的意思。
@RequiresPermissions (value={
"user:add", "user:del"}, logical= Logical.OR)
//已经授过权,调用Subject.isAuthenticated()返回true
@RequiresAuthentication
//身份验证或者通过记 住我登录的
@RequiresUser
Subject subject = SecurityUtils.getSubject();
//基于角色判断
if(subject.hasRole(“admin”)) {
//有角色,有权限
} else {
//无角色,无权限
}
//或者权限判断
if(subject.isPermitted("/user/add")){
//有权限
}else{
//无权限
}
常见API
subject.hasRole("xxx");
subject.isPermitted("xxx");
subject. isPermittedAll("xxxxx","yyyy");
subject.checkRole("xxx"); // 无返回值,可以认为内部使用断言的方式
技术选型:
前后端分离的权限检验 + SpringBoot2.x + Mysql + Mybatis + Shiro + Redis + IDEA + JDK8
项目依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--阿里巴巴druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<!--spring整合shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- shiro+redis缓存插件 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.1.0</version>
</dependency>
数据库配置
#==============================数据库相关配置========================================
spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mitday_shiro?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
spring.datasource.username =root
spring.datasource.password =123456
#使用阿里巴巴druid数据源,默认使用自带的
#spring.datasource.type =com.alibaba.druid.pool.DruidDataSource
#开启控制台打印sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# mybatis 下划线转驼峰配置,两者都可以
#mybatis.configuration.mapUnderscoreToCamelCase=true
mybatis.configuration.map-underscore-to-camel-case=true
select * from user u
left join user_role ur on u.id=ur.user_id
left join role r on ur.role_id = r.id
left join role_permission rp on r.id=rp.role_id
left join permission p on rp.permission_id=p.id
where u.id=1
- 继承 AuthorizingRealm
- 重写 doGetAuthorizationInfo
- 重写 doGetAuthenticationInfo
package top.mitday.shiro_combat.config;
import jdk.nashorn.internal.parser.Token;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import top.mitday.shiro_combat.entity.Permission;
import top.mitday.shiro_combat.entity.Role;
import top.mitday.shiro_combat.entity.User;
import top.mitday.shiro_combat.service.UserService;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义realm
*/
public class CustomRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 进行权限校验的时候调用A
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("用户授权 doGetAuthorizationInfo");
User newuser = (User)principals.getPrimaryPrincipal();
User user = userService.findAllUserInfoByUsername(newuser.getUsername());
List<String> stringRoleList = new ArrayList<>();
List<String> stringPermissionList = new ArrayList<>();
List<Role> roleList = user.getRoleList();
for (Role role : roleList){
stringRoleList.add(role.getName());
List<Permission> permissionList = role.getPermissionList();
for (Permission permission : permissionList){
if (permission != null){
stringPermissionList.add(permission.getName());
}
}
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRoles(stringRoleList);
simpleAuthorizationInfo.addStringPermissions(stringPermissionList);
return simpleAuthorizationInfo;
}
/**
* 用户登录的时候调用
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("用户登录 doGetAuthenticationInfo");
/**
* 从token中获取中户信息,token代表用户的输入
*/
String username = String.valueOf(token.getPrincipal());
User user = userService.findAllUserInfoByUsername(username);
//获取密码
String pwd = user.getPassword();
if (pwd == null || "".equals(pwd)){
return null; }
return new SimpleAuthenticationInfo(user,user.getPassword(),this.getClass().getName());
}
}
shiroFilterFactoryBean -> SecurityManager -> CustomSessionManager
CustomRealm -> hashedCredentialsMatcher
SessionManager
DefaultSessionManager: 默认实现,常用于javase
ServletContainerSessionManager: web环境
DefaultWebSessionManager:常用于自定义实现
package top.mitday.shiro_combat.config;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
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.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
System.out.println("执行 ShiroFilterFactoryBean.shiroFilter");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//必须设置securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
/**
* 需要登录的接口,如果访问某个接口,需要登录却没登录,则调用词接口
* 如果不是前后端分离,则跳转页面 ---> /xxx.jsp
*/
shiroFilterFactoryBean.setLoginUrl("/pub/need_login");
/**
* 登录成功,跳转url,如果前后端分离,则没有则个调用
*/
shiroFilterFactoryBean.setSuccessUrl("/");
/**
* 登录后没有权限,未授权就会调用此接口
* 先验证登录 ---> 再验证是否有权限
*/
shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit");
// 设置自定义filter
Map<String , Filter> filterMap = new LinkedHashMap<>();
filterMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
shiroFilterFactoryBean.setFilters(filterMap);
/**
* 拦截器路径
* 坑1:不放呢路径无法进行拦截,时有时无,因使用的是hashmap,无序的,应该为LinkedHashMap
*/
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
// 退出过滤器
filterChainDefinitionMap.put("/logout","logout");
// 匿名可以访问,也就是游客
filterChainDefinitionMap.put("/pub/**","anon");
// 登录用户才可以访问
filterChainDefinitionMap.put("/authc/**","authc");
// 管理员角色才可以访问
filterChainDefinitionMap.put("/admin/**","roleOrFilter[admin,root]");
// 有编辑权限才可以访问
filterChainDefinitionMap.put("/video/update","perms[video_update]");
/**
* authc : url定义必须通过认证才可以访问
* anon :可以匿名访问
* 坑2 : 过滤链是顺序执行,从上而下,一般/** 放到最下面
*/
filterChainDefinitionMap.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* SecurityManager 对customRealm、sessionManager进行绑定
* 因不能直接new customRealm()、new sessionManager()
* 所以得自定义customRealm、sessionManager 并@Bean让其Spring进行加载
* @return
*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 如果不是前后端分离,则不必设置下面的sessionManager
securityManager.setSessionManager(sessionManager());
//使用自定义cacheManager
securityManager.setCacheManager(cachManager());
// 设置realm (推荐放到最后,不然某些情况会不生效)
securityManager.setRealm(customRealm());
return securityManager;
}
/**
* 加载自定义CustomRealm
* 加载好后需要对其进行加密 setCredentialsMatcher
* @return
*/
@Bean
public CustomRealm customRealm(){
CustomRealm customRealm = new CustomRealm();
customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return customRealm;
}
/**
* 自定义HashedCredentialsMatcher
* 密码加解密
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// 设置散列算法 这里使用MD5算法
credentialsMatcher.setHashAlgorithmName("md5");
//散列次数,散列2次 相当于md5(md5(xxx))
credentialsMatcher.setHashIterations(2);
return credentialsMatcher;
}
/**
* 自定义SessionManager
* @return
*/
@Bean
public SessionManager sessionManager(){
CustomSessionManager customSessionManager = new CustomSessionManager();
/**
* 超时时间,默认 30 分钟 会话超时
* 方法里面的单位是毫秒
*/
customSessionManager.setGlobalSessionTimeout(20000);
//配置session持久化
customSessionManager.setSessionDAO(redisSessionDAO());
return customSessionManager;
}
/**
* 配置redisManqger
*/
public RedisManager getRedisManager(){
RedisManager redisManager = new RedisManager();
redisManager.setHost("localhost");
redisManager.setPort(6378);
return redisManager;
}
/**
* 配置具体cache实现类
* @return
*/
public RedisCacheManager cachManager(){
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(getRedisManager());
// 设置过期时间 单位是秒
redisCacheManager.setExpire(20);
return redisCacheManager;
}
/**
* 自定义session持久化
* @return
*/
public RedisSessionDAO redisSessionDAO(){
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(getRedisManager());
// 设置session id生成器
redisSessionDAO.setSessionIdGenerator(new CustomSessionIdGenerator());
return redisSessionDAO;
}
/**
* 管理shiro一些bean的生命周期 即bean初始化 与销毁
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* api controller层面
* 加入注解的使用,不加入这个AOP注解不生效(shiro的注解 例如 @RequiresGuest)
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
/**
* 用来扫描上下文寻找所有的Advistor(通知器), 将符合条件的Advisor应用到切入点的Bean中,
* 需要在LifecycleBeanPostProcessor创建后才可以创建
* @return
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
return defaultAdvisorAutoProxyCreator;
}
}
package top.mitday.shiro_combat.config;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
public class CustomSessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "token";
public CustomSessionManager(){
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
if (sessionId != null){
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
//automatically mark it valid here. If it is invalid, the
//onUnknownSession method below will be invoked and we'll remove the attribute at that time.
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return sessionId;
}else {
return super.getSessionId(request,response);
}
}
}
@Test
public void testMD5(){
//加密算法
String hashName = "md5";
//密码明文
String pwd = "123";
//加密函数,使用shiro自带的
Object result = new SimpleHash(hashName, pwd, null, 2);
System.out.println(result);
}
知识背景:
/admin/order= roles["admin, root"]
表示 /admin/order 这个接口需要用户同时具备 admin 与 root 角色才可访问
相当于hasAllRoles() 这个判断方法
需求:
订单信息,可以由角色 普通管理员 admin 或者 超级管理员 root 查看
只要用户具备其中一个角色即可
第一步:
package top.mitday.shiro_combat.config;
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.util.Set;
/**
* 自定义filter
*/
public class CustomRolesOrAuthorizationFilter extends AuthorizationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
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);
//当前subject是roles中任意一个,则有权限访问
for (String role : roles) {
if (subject.hasRole(role)){
return true;
}
}
return subject.hasAllRoles(roles);
}
}
第二步:
在Shiroconfig类的shiroFilter方法中添加
// 设置自定义filter
Map<String , Filter> filterMap = new LinkedHashMap<>();
filterMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
shiroFilterFactoryBean.setFilters(filterMap);
/**
* 拦截器路径
* 坑1:不放呢路径无法进行拦截,时有时无,因使用的是hashmap,无序的,应该为LinkedHashMap
*/
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/admin/**","roleOrFilter[admin,root]");
使用原因:
授权的时候每次都去查询数据库,对于频繁访问的接口,性能和响应速度比较慢,所以使用缓存
加依赖:
<!-- shiro+redis缓存插件 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.1.0</version>
</dependency>
//使用自定义的cacheManager
securityManager.setCacheManager(cacheManager());
/**
* 配置redisManager
*
*/
public RedisManager getRedisManager(){
RedisManager redisManager = new RedisManager();
redisManager.setHost("localhost");
redisManager.setPort(6379);
return redisManager;
}
/**
* 配置具体cache实现类
* @return
*/
public RedisCacheManager cacheManager(){
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(getRedisManager());
//设置过期时间,单位是秒,20s,
redisCacheManager.setExpire(20);
return redisCacheManager;
}
改造原有的逻辑,修改缓存的唯一key:
//doGetAuthorizationInfo 方法
//原有:
String username = (String)principals.getPrimaryPrincipal();
User user = userService.findAllUserInfoByUsername(username);
//改为
User newUser = (User)principals.getPrimaryPrincipal();
User user = userService.findAllUserInfoByUsername(newUser.getUsername());
//doGetAuthenticationInfo方法
//原有:
return new SimpleAuthenticationInfo(username, user.getPassword(), this.getClass().getName());
//改为
return new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getName());
为啥session也要持久化:
重启应用,用户无感知,可以继续以原先的状态继续访问
怎么持久化:
//配置session持久化
customSessionManager.setSessionDAO(redisSessionDAO());
/**
* 自定义session持久化
* @return
*/
public RedisSessionDAO redisSessionDAO(){
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(getRedisManager());
return redisSessionDAO;
}
注意点:
实体类对象需要实现序列化接口 Serializable
logout接口和以前一样调用,请求logout后会删除redis里面的对应的key,即删除对应的token
LifecycleBeanPostProcessor
作用:管理shiro一些bean的生命周期 即bean初始化 与销毁
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
AuthorizationAttributeSourceAdvisor
作用:加入注解的使用,不加入这个AOP注解不生效(shiro的注解 例如 @RequiresGuest)
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
DefaultAdvisorAutoProxyCreator
作用: 用来扫描上下文寻找所有的Advistor(通知器), 将符合条件的Advisor应用到切入点的Bean中,需要在LifecycleBeanPostProcessor创建后才可以创建
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
return defaultAdvisorAutoProxyCreator;
}
基于原先项目,实现自定义sessionid
Shiro 默认的sessionid生成 类名 SessionIdGenerator
创建一个类,实现 SessionIdGenerator 接口的方法
package top.mitday.shiro_combat.config;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
import java.io.Serializable;
import java.util.Random;
import java.util.UUID;
/**
* 自定义session生成
*/
public class CustomSessionIdGenerator implements SessionIdGenerator {
@Override
public Serializable generateId(Session session) {
return "mitday"+UUID.randomUUID().toString().replace("-","");
}
}
Shiro实战源码GitHub:https://github.com/GBugIT/springboot_shiro.git