【Shiro学习笔记】一、Shiro具体使用详解(基于springboot2.x,前后端分离)

2 篇文章 0 订阅

PS:欢迎转载,但请注明出处,谢谢配合。

一、前言

基于springboot2.x,前后端分离

二、具体步骤

1、加入shiro依赖包

修改pom.xml

<!-- 引入 shiro -->
<dependency>
    <groupId>org.apache.shiro</groupId>
     <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>

2、自定义认证过滤器FormAuthenticationFilter

目的:将原来登录校验不通过的重定向改为返回Json数据
做法:继承 authc 的默认认证过滤器(FormAuthenticationFilter)

public class MyFormAuthenticationFilter extends FormAuthenticationFilter {

1)重写onAccessDenied 方法
针对shiro配置认证规则为“authc”的那些url,当校验出用户没有登录的话,执行该方法

/**
 * 	当shiro校验用户未登录时,返回JSON数据(代替原有的跳转到登录界面)
 */
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
	this.log.error("用户未登录,拒绝访问");
	response.setContentType("application/json") ;
	response.setCharacterEncoding("UTF-8") ;
	PrintWriter out = response.getWriter() ;
	String resultJson = JSON.toJSONString(ResultUtil.error("IMS0000","登录已失效,请重新登录!")) ;
	out.write(resultJson) ;
	out.flush() ;
	out.close() ;
	return false ;
}

3、自定义授权过滤器RolesAuthorizationFilter

目的:将原来权限校验不通过的重定向改为返回Json数据
做法:继承 roles 的默认授权过滤器(RolesAuthorizationFilter)

public class MyRolesAuthorizationFilter extends RolesAuthorizationFilter{

1)重写 onAccessDenied 方法
针对shiro配置授权规则为“roles”的那些url,当校验出用户没有对应权限的话,执行该方法。
返回json数据格式,由前端决定授权拒绝的后续逻辑。
写法参考上面的自定义认证过滤器MyFormAuthenticationFilter。

2)重写 isAccessAllowed 方法(可选)
若role配置多个,默认情况下,是必须多个role同时具备,才有访问权限。但一般实际情况,是只要拥有配置中的某个权限,即可访问。因此可以重写isAccessAllowed 方法,将父类中的“and”逻辑改为“or”逻辑。

// 原逻辑,多个role必须同时满足,才有权限访问
// return subject.hasAllRoles(roles);   

// 修改后逻辑 -- begin (多个role之间是“或”关系,只有一个满足即可)
for(String role: roles) {
	if(subject.hasRole(role)) {
		return true ;
	}
}
return false ;
// 修改后逻辑 --end

4、编写shiro配置类

1)编写一个shiro的配置类

@Configuration
public class ShiroConfig {

2)配置类中,定义shiro的过滤器工厂bean
在该过滤器工厂中,可以 设置自定义过滤器、配置认证规则、配置授权规则

@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
	// 创建 ShiroFilterFactoryBean
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    
    // 1、创建过滤器Map,用来装自定义过滤器
    LinkedHashMap<String, Filter> map = new LinkedHashMap<>();
    // 2、将自定义过滤器放入map中,如果实现了自定义授权过滤器,那就必须在这里注册,否则Shiro不会使用自定义的授权过滤器
    map.put("authc", new MyFormAuthenticationFilter());	// 认证过滤器
    map.put("roles", new MyRolesAuthorizationFilter()); // 授权过滤器  
    // 3、将过滤器绑定到shiroFilterFactoryBean上
    shiroFilterFactoryBean.setFilters(map);
    // 4.配置认证规则 // <!-- authc:所有url都必须认证通过才可以访问;  anon:所有url都都可以匿名访问-->
    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    filterChainDefinitionMap.put("/login/**", "anon");	// 类似/login/xxx请求,可以直接访问
    filterChainDefinitionMap.put("/jnl/**", "anon");	// 类似/jnl/xxx请求,可以直接访问
    // 5.配置授权规则
    filterChainDefinitionMap.put("/admin/**", "roles[admin]");	// 类似/admin/**请求,需要admin角色才可访问(可以写多个role,默认是多个role同时满足,可以自定义改成或关系)
    //注意:下面这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截,都需要认证(因为filterChainDefinitions 配置过滤规则,是从上到下的顺序匹配)
    filterChainDefinitionMap.put("/**", "authc");		// 剩下的其他请求,需要登录后才可访问
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    
    return shiroFilterFactoryBean;
}

3)配置类中,定义权限管理bean
主要包装我们编写的业务Realm,该bean会注给上面的过滤器工厂。shiro过滤器拦截的请求,会通过SecurityManager,走到业务Realm的相关方法。

@Bean
public SecurityManager securityManager(UserRealm myShiroRealm) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(myShiroRealm);
    return securityManager;
}

4)配置类中,定义业务Realm bean
下面代码中的两个参数,是我项目中的数据库操作Service,大家根据自己的情况实现即可。或者先不用数据库操作,直接代码中模拟数据库的返回值,先试试Shiro效果,则不用注入下面两个参数。

@Bean  
public UserRealm myShiroRealm(ShiroUserService shiroUserServiceImpl, ShiroUserRoleService shiroUserRoleServiceImpl) {
    UserRealm userRealm = new UserRealm();
    userRealm.setShiroUserService(shiroUserServiceImpl) ;
    userRealm.setShiroUserRoleService(shiroUserRoleServiceImpl) ;
    return userRealm;
}

5、编写Realm类

目的:操作数据层,实现用户的认证和授权
做法:继承Realm父类 AuthorizingRealm

public class UserRealm extends AuthorizingRealm {

1)实现 doGetAuthenticationInfo(认证方法)

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
	UsernamePasswordToken upToken = (UsernamePasswordToken) token;
	String userName = upToken.getPrincipal().toString() ;
	// 1.根据用户名,查询用户信息
    Map userMap = this.shiroUserService.queryUser(userName) ;
    // 2.判断用户名是否存在
    if(null == userMap || userMap.isEmpty()) {
    	return null ;  // 这里返回null后,后面逻辑会报出对应异常(账号不存在)
    }
    // 3.根据salt,将前端上送的密码,加盐加密后,重新放入原token,以便后续逻辑比对两个加密后的密码是否一致【我项目中数据库存储的用户密码是MD5加盐加密后的,明文存储的则不需要这步】
    String salt = (String) userMap.get("salt") ; 			// 密码加密用的盐值
    char[] oldPassword = upToken.getPassword() ;
    upToken.setPassword(this.md5withSalt(String.valueOf(oldPassword), salt).toCharArray());
    String password = (String) userMap.get("password") ; 	//DB中的密码(加密后的)
    // 4.根据查询出的用户名/密码,构建SimpleAuthenticationInfo认证对象(第一个参数: 用户名 ;第二个参数:DB中查到的密码 ; 第三个参数:当前Realm名字)
    return new SimpleAuthenticationInfo(userName, password, this.getClass().getName()) ;  //shiro后续会根据该对象,与原上送参数token对象,比对两者的密码是否一致,不一致则抛出IncorrectCredentialsException异常
}

登录交易中,会调用Subject.login(token),最终会执行到Realm 的 doGetAuthenticationInfo 方法。

2)实现 doGetAuthorizationInfo(授权方法)

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
	// 1. 获取授权的用户
	String userName = principals.getPrimaryPrincipal().toString() ;
	// 2. 根据用户名,从DB中查询拥有的角色(另:实际可进行缓存优化,不应每次都查库)
	Set<String> roles = this.shiroUserRoleService.queryUserRoles(userName) ;
	// 3. 根据角色集合Set<String>,构建SimpleAuthorizationInfo授权对象
	return new SimpleAuthorizationInfo(roles);
}

shiro过滤器拦截请求后,若对应了role规则配置的路径,则会判断是否具有role角色权限,通过RolesAuthorizationFilter的 isAccessAllowed方法,最终会执行到Realm 的 doGetAuthorizationInfo 方法。

6、编写登录Controller

登录交易中,调用shiro相关类进行认证,主要包括如下内容:

Subject subject = SecurityUtils.getSubject() ; // 获取Subject-用户主体(会把操作交给SecurityManager,最后到Realm)
AuthenticationToken token = new UsernamePasswordToken(userName, passWord) ; // 将从获取的用户名和密码设置到一个token中

subject.login(token); // 通过捕获该方法抛出的异常,返回对应报错信息给前端(UnknownAccountException-用户名错误,IncorrectCredentialsException-密码错误)
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值