在上一篇文章 Post not found: Spring Boot集成shiro步骤 Spring Boot集成shiro步骤 中,我简单的写了一下在SpringBoot中集成Shiro的步骤。年代比较久远了,而且当时也是刚刚学习Shiro框架,不太懂,时隔很长时间后,再次使用Shiro,然后争取能对Shiro的权限认证有一个更加深入的理解。
参考文章:
1.使用 Shiro 设计基于用户、角色、权限的通用权限管理系统
2.shiro的authc和user拦截器的一点区别
3.Shiro拦截AJAX的解决方案
4.shiro登录认证后不执行授权doGetAuthorizationInfo的解决
5.Shiro学习笔记(2)——身份验证之Realm
1.密码比对
在进行密码比对的时候,UsernamePasswordToken内部将密码部分转为字符数组,所以如果数据库中存储的是明文信息,那么就行比对的时候,就要将传入的信息进行转换一下toCharArray() 方法将数据库中的明文进行转换一下,才能进行正确的密码比对。
1 2 3 4 5 6 7 8 9 10 11 12 13
| try{ SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( userInfo.getUsername(), userInfo.getPassword().toCharArray() , getName() ); return authenticationInfo; }catch (Exception e){ System.out.println("授权失败"); logger.error("doGetAuthenticationInfo",userInfo); return null; }
|
在进行密码比对时的逻辑如下:
密码比对是在 SimpleCredentialsMatcher 类中的 doCredentialsMatch 方法中进行密码比对的,比对的方式也很简单,直接使用了对用户输入的密码和数据库中的密码生成 byte 数组然后进行比较,最终的比较在 MessageDigest 类的 isEqual 方法中。部分逻辑如下:
参考文章:
1.【Shiro权限管理】8.Shiro密码的比对
2.shiro中的密码是如何验证是否匹配的
3.Spring Boot and OAuth2
4.shiro自定义密码匹配验证,密码加密验证
5.【Shiro 系列 06】Shiro 中密码加密
6.springboot集成shiro时认证出现报错(Submitted credentials for token…) (这篇文章提到了UsernamePasswordToken内部将密码部分转为字符数组)
7.Java toCharArray() 方法
8.【Shiro 系列 06】Shiro 中密码加密
9.shiro登录拦截
2.SessionManage
参考文章:
1.springboot + shiro之登录人数限制、登录判断重定向、session时间设置
3.ajax拦截
关于ajax请求的判断,有人说使用请求头里面的X-Requested-With,但是像普通的ajax是没有这个请求的。
参考文章:
1.Spring Boot 整合Shiro拦截Ajax请求
2.Spring Boot 整合Shiro拦截Ajax请求
3.shiro登录拦截
4.如何判断请求是否是AJAX请求
5.慎用X-Requested-With判断是否是Ajax请求
6.easyui form ajax提交 requet头里 没有X-Requested-With
4.shiro和OAuth2
参考文章:
1.Shiro和OAuth2 集成
2.shiro 整合oauth2.0 服务端 和 客户端实现(入门教程)(十三) (这篇代码比较详细,可以直接照着做)
3.PC端网站-微信扫码登录+shiro权限框架免密登录
4.使用Shiro和token进行无状态登录
5.自定义过滤器
我自定义了一个过滤器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class FormLoginFilter extends PathMatchingFilter { @Override protected boolean onPreHandle(ServletRequest request,ServletResponse response, Object mappedValue) throws Exception { Subject subject = SecurityUtils.getSubject(); boolean isAuthenticated = subject.isAuthenticated(); HttpServletResponse resp = (HttpServletResponse) response; HttpServletRequest httpRequest = (HttpServletRequest) request; if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) { return true; }
if (!isAuthenticated) { resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); resp.setContentType("text/html;charset=UTF-8"); resp.getWriter().print("{\"state\":\"403\",\"msg\":\"未授权\"}"); return false; } return true; } }
|
然后注入到shiro中,本意是想让所有需要授权的接口,都走这个过滤器,而不需要认证的接口,都不走这个过滤器链,比如/login.但是似乎是事与愿违,所有的请求,都会走这个过滤器,造成了无法估量的损失。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| @Configuration public class BaseSecurityConfig { @Bean public FormLoginFilter formLoginFilter(){ return new FormLoginFilter(); }
@Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, Filter> filters = shiroFilterFactoryBean.getFilters(); filters.put("authc", formLoginFilter()); shiroFilterFactoryBean.setLoginUrl("/"); shiroFilterFactoryBean.setSuccessUrl("/"); shiroFilterFactoryBean.setUnauthorizedUrl("/403");
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); filterChainDefinitionMap.put("/static/**","anon"); filterChainDefinitionMap.put("/user/register", "authc"); filterChainDefinitionMap.put("/user/checkloginByCode", "anon"); filterChainDefinitionMap.put("/user/**", "anon"); filterChainDefinitionMap.put("/", "anon"); filterChainDefinitionMap.put("/loginWX", "anon"); filterChainDefinitionMap.put("/get_auth_access_token", "anon"); filterChainDefinitionMap.put("/scan", "anon"); filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean; } }
|
我尝试使用参考文章中的方法,使用如下的内容进行取消注入,但是失效了。
1 2 3 4 5 6
| @Bean public FilterRegistrationBean registration(FormLoginFilter filter) { FilterRegistrationBean registration = new FilterRegistrationBean(filter); registration.setEnabled(false); return registration; }
|
最后只能通过继承ApplicationContextAware接口,通过手动的方式获取Bean,比如参考文章4中说到的一样。至于如何获取到@Value中的值,那就和参考文章7中的一样就好了。SpringContextUtil工具类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| @Component public class SpringContextUtil implements ApplicationContextAware { private static ApplicationContext applicationContext;
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringContextUtil.applicationContext = applicationContext; }
public static ApplicationContext getApplicationContext() { return applicationContext; }
public static Object getBean(String name) { return applicationContext.getBean(name); }
public static Object getBean(Class<?> requiredType) { return applicationContext.getBean(requiredType); }
public static String getValue(String name){ return applicationContext.getEnvironment().getProperty(name); }
}
|
参考文章:
1.SpringBoot 整合Shiro 之 自定义Filter
2.Shiro-实战(四)—过滤器机制 (写了各种拦截器的作用)
3.Shiro 自定义 filter 匹配异常(好文) (这篇文章中出现了同样的问题,还有问题的方法求解过程,但是最后的结论有点差强人意,就是说不要把自定义的filter注册为Bean,也就是使用filters.put(“authc”, new FormLoginFilter()),代替filters.put(“authc”, formLoginFilter()),这显然不符合我们的预期,因为我要在Filter中使用其他的Bean)
4.Spring Boot 自定义 Shiro 过滤器,无法使用 @Autowired 解决方法 (这篇文章也是修改了注入方法,同样出现了问题,于是就使用了创建的SpringContextUtil工具类,手动获取Bean的方法,来使用@Autowired,这是是解决我问题的主要方法)
5.springboot2整合shiro时@value注解无效解决方案 (这里是讲如何在过滤器中使用@Value进行属性注入的方法,主要是将生命周期静态化,但是这中方法很不适用,或者说与我的需求有些不符合)
6.Spring拦截器中@Value无效的解决办法 (这个是将Filter当作Bean注入到Spring中,与我的问题相冲突)
7.不用@Value从Spring的ApplicationContext中获取一个或全部配置 (这里是如何使用applicationContext读取application.yml中的配置,方法还是很有用的)
8.filter过滤器使用Autowired注入Bean为null (在init方法中使用filterConfig参数,如果在dofilter方法中也可以使用request参数,这是两种获取Bean的方法,都是借助于WebApplicationContextUtils.getWebApplicationContext这个方法)