当前位置:   article > 正文

springboot集成shiro完成token认证,以及需要注意的点_springboot shiro token

springboot shiro token

  依赖:

  1. <dependency>
  2. <groupId>org.apache.shiro</groupId>
  3. <artifactId>shiro-spring-boot-web-starter</artifactId>
  4. <version>1.4.1</version>
  5. </dependency>

1.自定义token继承UsernamePasswordToken

  1. public class JwtToken extends UsernamePasswordToken {
  2. private String token;
  3. public String getToken() {
  4. return token;
  5. }
  6. public void setToken(String token) {
  7. this.token = token;
  8. }
  9. public JwtToken(String username, String password) {
  10. super(username, password);
  11. }
  12. }

注意 :继承 UsernamePasswordToken 类后,账号跟密码使用了 UsernamePasswordToken 类的构造方法

2.自定义Realm

  1. import org.apache.shiro.authc.AuthenticationException;
  2. import org.apache.shiro.authc.AuthenticationInfo;
  3. import org.apache.shiro.authc.AuthenticationToken;
  4. import org.apache.shiro.authc.SimpleAuthenticationInfo;
  5. import org.apache.shiro.authz.AuthorizationInfo;
  6. import org.apache.shiro.authz.SimpleAuthorizationInfo;
  7. import org.apache.shiro.realm.AuthorizingRealm;
  8. import org.apache.shiro.subject.PrincipalCollection;
  9. public class CustomerRealm extends AuthorizingRealm {
  10. @Override
  11. public boolean supports(AuthenticationToken token) {
  12. return token instanceof JwtToken;
  13. }
  14. //授权
  15. @Override
  16. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
  17. SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
  18. /*
  19. 常见的方法:
  20. addRole(String role):向授权信息中添加角色。
  21. addRoles(Collection<String> roles):向授权信息中添加多个角色。
  22. addStringPermission(String permission):向授权信息中添加权限(字符串形式)。
  23. addStringPermissions(Collection<String> permissions):向授权信息中添加多个权限(字符串形式)。
  24. setRoles(Set<String> roles):设置授权信息的角色集合。
  25. setStringPermissions(Set<String> permissions):设置授权信息的权限集合。
  26. getRoles():获取授权信息中的角色集合。
  27. getStringPermissions():获取授权信息中的权限集合。
  28. */
  29. // 在这里只是设置权限了一个普通的权限:字符串
  30. authorizationInfo.addStringPermission("user:add:*");
  31. return authorizationInfo;
  32. }
  33. //认证
  34. @Override
  35. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
  36. JwtToken jwtToken = (JwtToken) authenticationToken;
  37. return new SimpleAuthenticationInfo("任意对象","147258369",this.getName());
  38. }
  39. }

  • 自定义Realm注意的点:

第一点:

        在 doGetAuthenticationInfo 方法里面,他的返回值:
 

return new SimpleAuthenticationInfo("任意对象","147258369",this.getName());

SimpleAuthenticationInfo 对象是要重点说一下的:

  • 第一个参数是验证成功之后保存的用户数据,是一个Object类型,
  • 第二个参数传递的是要做密码比较的参数:

 首先来看下这个图,这个是我模拟登录时候的代码,可以看到我把账号跟密码传递给了 JwtToken:


而 JwtToken 继承了  UsernamePasswordToken 类,继续点进源码:

 可以看到在 UsernamePasswordToken  类中获取账号跟密码相对应的方法,然后我在登录的controller中,继续调用以下方法进行登录:

  1. Subject subject = SecurityUtils.getSubject();
  2. subject.login(token);

以上代码执行之后,会进入 doGetAuthenticationInfo 这个方法里面,进行登录认证(拿账号密码去数据库里面查询)成功之后,如果SimpleAuthenticationInfo 的第二个参数跟我传递给 new JwtToken("123456789","147258369") 的第二个参数不一样的话,就会认证失败,个人觉得 SimpleAuthenticationInfo 的第二个参数类型是

 而在登录的时候,传递进来的 JwtToken 继承的 UsernamePasswordToken类的密码:

命名一样,所以我觉得会做比较,如果不一致,会报错异常:(个人猜测)

如果登录的时候,认证失败:会返回:IncorrectCredentialsException 异常



第二点(进入doGetAuthorizationInfo 方法的时机):

  • doGetAuthorizationInfo方法不是主动调用的,而是被动调用的,当请求的接口上有类似这种 RequiresPermissions 注解的时候,就会去执行方法,判断是否有对应的权限

执行流程(请求设置了拦截路径的接口):在过滤器中使用 shrio 的登录方法,进入doGetAuthenticationInfo 方法中进行认证,认证成功之后会请求接口,如果接口上有shiro的权限或者角色注解,那么就会进入 doGetAuthorizationInfo 方法中获取相对应的权限或者角色,然后就会再返回接口中,如果有权限就执行接口的方法



第三点:我在测试的时候,发现加入了AOP依赖之后,对于放行路径,过滤器一样会进行拦截,目前这个我还没找到解决方法,只能无奈把AOP依赖去掉了(这个不知道你们会不会这样)

第四点:

 

3.设置shrio的配置类

  1. import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
  2. import org.apache.shiro.mgt.DefaultSubjectDAO;
  3. import org.apache.shiro.realm.Realm;
  4. import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
  5. import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.context.annotation.Configuration;
  8. import javax.servlet.Filter;
  9. import java.util.HashMap;
  10. import java.util.LinkedHashMap;
  11. import java.util.Map;
  12. @Configuration
  13. public class shiroConfig {
  14. //创建shirofilter,负责拦截所有请求
  15. @Bean(name = "shiroFilterFactoryBean")
  16. public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
  17. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
  18. shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
  19. Map<String, Filter> filters = new HashMap<>();
  20. filters.put("auth", new MyFilter());
  21. shiroFilterFactoryBean.setFilters(filters);
  22. //配置系统受限资源
  23. //配置系统公共资源
  24. Map<String,String> map = new LinkedHashMap<>();
  25. // 在这里,我放行请求的的是我的登录接口
  26. map.put("/user/login","anon");
  27. // 其余接口我都要进行拦截 ,拦截器是 new MyFilter()
  28. map.put("/**","auth");
  29. shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
  30. return shiroFilterFactoryBean;
  31. }
  32. //创建shiro安全管理器
  33. @Bean
  34. public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
  35. DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
  36. //关闭shiro自带的session
  37. DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
  38. DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
  39. defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
  40. subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
  41. defaultWebSecurityManager.setSubjectDAO(subjectDAO);
  42. //配置realm
  43. defaultWebSecurityManager.setRealm(realm);
  44. return defaultWebSecurityManager;
  45. }
  46. //创建自定义realm
  47. @Bean
  48. public Realm getRealm(){
  49. CustomerRealm customerRealm = new CustomerRealm();
  50. //设置缓存管理器
  51. customerRealm.setCachingEnabled(false);
  52. customerRealm.setAuthenticationCachingEnabled(true);
  53. customerRealm.setAuthenticationCacheName("AuthenticationCache");
  54. customerRealm.setAuthorizationCachingEnabled(true);
  55. customerRealm.setAuthorizationCacheName("AuthorizationCache");
  56. return customerRealm;
  57. }
  58. }
  • 配置类要注意的点:

        在配置拦截放行路径的时候,一定不能使用 hashmap,而是使用 LinkedHashMap ,然后配置路径的时候,不用拦截的请求放在前面,一般 /** 是放在最后的:

4.配置过滤器:

  1. package com.example.mp_pring.config.shiro;
  2. import org.apache.shiro.SecurityUtils;
  3. import org.apache.shiro.authc.AuthenticationException;
  4. import org.apache.shiro.subject.Subject;
  5. import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
  6. import org.springframework.http.HttpStatus;
  7. import org.springframework.stereotype.Component;
  8. import org.springframework.web.bind.annotation.RequestMethod;
  9. import javax.servlet.ServletRequest;
  10. import javax.servlet.ServletResponse;
  11. import javax.servlet.http.HttpServletRequest;
  12. import javax.servlet.http.HttpServletResponse;
  13. @Component
  14. public class MyFilter extends BasicHttpAuthenticationFilter {
  15. /**
  16. * 执行登录认证
  17. *
  18. * @param request
  19. * @param response
  20. * @param mappedValue
  21. * @return
  22. */
  23. @Override
  24. protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
  25. try {
  26. return executeLogin(request, response);
  27. } catch (Exception e) {
  28. System.out.println("JwtFilter过滤认证失败!");
  29. return false;
  30. }
  31. }
  32. @Override
  33. protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
  34. // token 一般都是放在请求头中
  35. HttpServletRequest httpServletRequest = (HttpServletRequest) request;
  36. String token = httpServletRequest.getHeader("Token");
  37. // 假设已经拿到token,并校验token了,如果校验不通过就直接 return false;
  38. // token校验成功,拿到账号跟密码
  39. JwtToken jwtToken = new JwtToken("123456789","147258369");
  40. try {
  41. // 提交给realm进行登入,如果错误他会抛出异常并被捕获
  42. Subject subject = SecurityUtils.getSubject();
  43. subject.login(jwtToken);
  44. // 如果没有抛出异常则代表登入成功,返回true
  45. return true;
  46. } catch (AuthenticationException e) {
  47. response.setCharacterEncoding("utf-8");
  48. response.getWriter().print("error");
  49. return false;
  50. }
  51. }
  52. /**
  53. * 对跨域提供支持
  54. * @param request
  55. * @param response
  56. * @return
  57. * @throws Exception
  58. */
  59. @Override
  60. protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
  61. HttpServletRequest httpServletRequest = (HttpServletRequest) request;
  62. HttpServletResponse httpServletResponse = (HttpServletResponse) response;
  63. httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
  64. httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
  65. httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
  66. // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
  67. if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
  68. httpServletResponse.setStatus(HttpStatus.OK.value());
  69. return false;
  70. }
  71. return super.preHandle(request, response);
  72. }
  73. }

5.演示:

  1. import org.apache.shiro.SecurityUtils;
  2. import org.apache.shiro.authz.annotation.RequiresPermissions;
  3. import org.apache.shiro.subject.PrincipalCollection;
  4. import org.apache.shiro.subject.Subject;
  5. import org.springframework.stereotype.Controller;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.ResponseBody;
  8. import javax.servlet.http.HttpServletResponse;
  9. /**
  10. * @Description:
  11. * @Author sk
  12. * @Date: 2023/4/4 18:40
  13. */
  14. @Controller
  15. @RequestMapping("/user")
  16. public class UserControllerabc {
  17. @RequestMapping("/login")
  18. @ResponseBody
  19. public String login() throws Exception {
  20. JwtToken token = new JwtToken("123456789","147258369");
  21. try {
  22. Subject subject = SecurityUtils.getSubject();
  23. subject.login(token);
  24. System.out.println("认证成功");
  25. }catch (Exception e){
  26. e.printStackTrace();
  27. System.out.println("认证失败");
  28. }
  29. return "success";
  30. }
  31. @RequestMapping("/test")
  32. @ResponseBody
  33. @RequiresPermissions("user:add:*")
  34. public String test() {
  35. Subject subject = SecurityUtils.getSubject();
  36. Object principal = subject.getPrincipal();
  37. PrincipalCollection principals = subject.getPrincipals();
  38. return "success:验证成功:user:add:*";
  39. }
  40. @RequestMapping("/test1")
  41. @ResponseBody
  42. @RequiresPermissions("user:add:a")
  43. public String test1() {
  44. return "success:验证成功:user:add:a";
  45. }
  46. }

 这两个接口需要权限为 user:add:* 和 user:add:a 才能访问,在本案例中:

设置的权限就是 user:add:* ,所以按道理来说,test跟test1接口都能访问,* 代表的通配符

可以看到,都访问成功了 

  • 修改权限

我在这里把权限设置为了  user:add:a  ,所以无法访问test接口,尝试一波:

 


 可以看到,如果没有权限,会直接报错

6.演示文件:

        本项目已经上传到本博客,需要的可自行下载,可直接使用

7.注意!!!!!:

        我用了shrio之后,在controller层直接使用了事务的注解,导致这个controller里面的接口全报404了(但是这个在我另外一个项目里面没有出现),查了一下,是 shrio 跟 Transactional 的aop 执行时间有关系:可以参考一下别人的博客:        在Controller上使用@Transactional注解导致的接口404_@transactional controller_木子的木木的博客-CSDN博客

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/368073
推荐阅读
相关标签
  

闽ICP备14008679号