前言

目的:

通过注解完成权限控制。

源码下载:Spring-Boot-Shiro-master.zip

效果:

image.png

操作

代码一览

image.png

maven引用

  1. <dependency>
  2. <groupId>org.apache.shiro</groupId>
  3. <artifactId>shiro-spring</artifactId>
  4. <version>1.3.2</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.auth0</groupId>
  8. <artifactId>java-jwt</artifactId>
  9. <version>3.2.0</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-web</artifactId>
  14. <version>1.5.8.RELEASE</version>
  15. </dependency>
  16. <!--lombok-->
  17. <dependency>
  18. <groupId>org.projectlombok</groupId>
  19. <artifactId>lombok</artifactId>
  20. <version>1.18.4</version>
  21. </dependency>

基础代码

ShiroUserBean

  1. @Data
  2. @Builder
  3. @NoArgsConstructor
  4. @AllArgsConstructor
  5. @FieldNameConstants
  6. public class ShiroUserBean {
  7. private String username;
  8. private String password;
  9. private String role;
  10. private String permission;
  11. }

JWTUtil

  1. import com.auth0.jwt.JWT;
  2. import com.auth0.jwt.JWTVerifier;
  3. import com.auth0.jwt.algorithms.Algorithm;
  4. import com.auth0.jwt.exceptions.JWTDecodeException;
  5. import com.auth0.jwt.interfaces.DecodedJWT;
  6. import org.inlighting.database.ShiroUserBean;
  7. import java.io.UnsupportedEncodingException;
  8. import java.util.Date;
  9. public class JWTUtil {
  10. private static String SECRET = "123SDFEEdfdeFDRE";
  11. /**
  12. * 过期时间1000 60 秒 60 分钟 24 小时 5 天
  13. */
  14. private static final long EXPIRE_TIME = 1000*60*60*24*5;
  15. /**
  16. * 校验token是否正确,是否过期
  17. * @param token 密钥
  18. * @return 是否正确
  19. */
  20. public static boolean verify(String token) {
  21. try {
  22. Algorithm algorithm = Algorithm.HMAC256(SECRET);
  23. JWTVerifier verifier = JWT.require(algorithm).build();
  24. DecodedJWT jwt = verifier.verify(token);
  25. return true;
  26. } catch (Exception exception) {
  27. return false;
  28. }
  29. }
  30. /**
  31. * 获得token中的信息
  32. * @return ShiroUserBean
  33. */
  34. public static ShiroUserBean getUserBean(String token) {
  35. try {
  36. DecodedJWT jwt = JWT.decode(token);
  37. ShiroUserBean shiroUserBean = new ShiroUserBean();
  38. shiroUserBean.setUsername(jwt.getClaim(ShiroUserBean.Fields.username).asString());
  39. shiroUserBean.setRole(jwt.getClaim(ShiroUserBean.Fields.role).asString());
  40. shiroUserBean.setPermission(jwt.getClaim(ShiroUserBean.Fields.permission).asString());
  41. return shiroUserBean;
  42. } catch (JWTDecodeException e) {
  43. return null;
  44. }
  45. }
  46. /**
  47. * 生成签名,5min后过期
  48. * @param shiroUserBean 用户名
  49. * @return 加密的token
  50. */
  51. public static String sign(ShiroUserBean shiroUserBean) {
  52. try {
  53. Date date = new Date(System.currentTimeMillis()+EXPIRE_TIME);
  54. Algorithm algorithm = Algorithm.HMAC256(SECRET);
  55. // 附带username信息
  56. return JWT.create()
  57. .withClaim(ShiroUserBean.Fields.username, shiroUserBean.getUsername())
  58. .withClaim(ShiroUserBean.Fields.role, shiroUserBean.getRole())
  59. .withClaim(ShiroUserBean.Fields.permission, shiroUserBean.getPermission())
  60. .withExpiresAt(date)
  61. .sign(algorithm);
  62. } catch (UnsupportedEncodingException e) {
  63. return null;
  64. }
  65. }
  66. }

UnauthorizedException

  1. public class UnauthorizedException extends RuntimeException {
  2. public UnauthorizedException(String msg) {
  3. super(msg);
  4. }
  5. public UnauthorizedException() {
  6. super();
  7. }
  8. }

shiro配置

直接复制接口,基本上不变

JWTToken

  1. import org.apache.shiro.authc.AuthenticationToken;
  2. public class JWTToken implements AuthenticationToken {
  3. // 密钥
  4. private String token;
  5. public JWTToken(String token) {
  6. this.token = token;
  7. }
  8. @Override
  9. public Object getPrincipal() {
  10. return token;
  11. }
  12. @Override
  13. public Object getCredentials() {
  14. return token;
  15. }
  16. }

JWTFilter

  1. import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
  2. import org.slf4j.Logger;
  3. import org.slf4j.LoggerFactory;
  4. import org.springframework.http.HttpStatus;
  5. import org.springframework.web.bind.annotation.RequestMethod;
  6. import javax.servlet.ServletRequest;
  7. import javax.servlet.ServletResponse;
  8. import javax.servlet.http.HttpServletRequest;
  9. import javax.servlet.http.HttpServletResponse;
  10. import java.io.IOException;
  11. public class JWTFilter extends BasicHttpAuthenticationFilter {
  12. private Logger LOGGER = LoggerFactory.getLogger(this.getClass());
  13. /**
  14. * 判断用户是否想要登入。
  15. * 检测header里面是否包含Authorization字段即可
  16. */
  17. @Override
  18. protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
  19. HttpServletRequest req = (HttpServletRequest) request;
  20. String authorization = req.getHeader("Authorization");
  21. return authorization != null;
  22. }
  23. /**
  24. *
  25. */
  26. @Override
  27. protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
  28. HttpServletRequest httpServletRequest = (HttpServletRequest) request;
  29. String authorization = httpServletRequest.getHeader("Authorization");
  30. JWTToken token = new JWTToken(authorization);
  31. // 提交给realm进行登入,如果错误他会抛出异常并被捕获
  32. getSubject(request, response).login(token);
  33. // 如果没有抛出异常则代表登入成功,返回true
  34. return true;
  35. }
  36. /**
  37. * 这里我们详细说明下为什么最终返回的都是true,即允许访问
  38. * 例如我们提供一个地址 GET /article
  39. * 登入用户和游客看到的内容是不同的
  40. * 如果在这里返回了false,请求会被直接拦截,用户看不到任何东西
  41. * 所以我们在这里返回true,Controller中可以通过 subject.isAuthenticated() 来判断用户是否登入
  42. * 如果有些资源只有登入用户才能访问,我们只需要在方法上面加上 @RequiresAuthentication 注解即可
  43. * 但是这样做有一个缺点,就是不能够对GET,POST等请求进行分别过滤鉴权(因为我们重写了官方的方法),但实际上对应用影响不大
  44. */
  45. @Override
  46. protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
  47. if (isLoginAttempt(request, response)) {
  48. try {
  49. executeLogin(request, response);
  50. } catch (Exception e) {
  51. response401(request, response);
  52. }
  53. }
  54. return true;
  55. }
  56. /**
  57. * 对跨域提供支持
  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. /**
  74. * 将非法请求跳转到 /401
  75. */
  76. private void response401(ServletRequest req, ServletResponse resp) {
  77. try {
  78. HttpServletResponse httpServletResponse = (HttpServletResponse) resp;
  79. httpServletResponse.sendRedirect("/401");
  80. } catch (IOException e) {
  81. LOGGER.error(e.getMessage());
  82. }
  83. }
  84. }

ShiroConfig

  1. import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
  2. import org.apache.shiro.mgt.DefaultSubjectDAO;
  3. import org.apache.shiro.spring.LifecycleBeanPostProcessor;
  4. import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
  5. import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
  6. import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
  7. import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
  8. import org.springframework.context.annotation.Bean;
  9. import org.springframework.context.annotation.Configuration;
  10. import org.springframework.context.annotation.DependsOn;
  11. import javax.servlet.Filter;
  12. import java.util.HashMap;
  13. import java.util.Map;
  14. @Configuration
  15. public class ShiroConfig {
  16. @Bean("securityManager")
  17. public DefaultWebSecurityManager getManager(MyRealm realm) {
  18. DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
  19. // 使用自己的realm
  20. manager.setRealm(realm);
  21. /*
  22. * 关闭shiro自带的session,详情见文档
  23. * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
  24. */
  25. DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
  26. DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
  27. defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
  28. subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
  29. manager.setSubjectDAO(subjectDAO);
  30. return manager;
  31. }
  32. @Bean("shiroFilter")
  33. public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager) {
  34. ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
  35. // 添加自己的过滤器并且取名为jwt
  36. Map<String, Filter> filterMap = new HashMap<>();
  37. filterMap.put("jwt", new JWTFilter());
  38. factoryBean.setFilters(filterMap);
  39. factoryBean.setSecurityManager(securityManager);
  40. factoryBean.setUnauthorizedUrl("/401");
  41. /*
  42. * 自定义url规则
  43. * http://shiro.apache.org/web.html#urls-
  44. */
  45. Map<String, String> filterRuleMap = new HashMap<>();
  46. // 所有请求通过我们自己的JWT Filter
  47. filterRuleMap.put("/**", "jwt");
  48. // 访问401和404页面不通过我们的Filter
  49. filterRuleMap.put("/401", "anon");
  50. factoryBean.setFilterChainDefinitionMap(filterRuleMap);
  51. return factoryBean;
  52. }
  53. /**
  54. * 下面的代码是添加注解支持
  55. */
  56. @Bean
  57. @DependsOn("lifecycleBeanPostProcessor")
  58. public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
  59. DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
  60. // 强制使用cglib,防止重复代理和可能引起代理出错的问题
  61. // https://zhuanlan.zhihu.com/p/29161098
  62. defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
  63. return defaultAdvisorAutoProxyCreator;
  64. }
  65. @Bean
  66. public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
  67. return new LifecycleBeanPostProcessor();
  68. }
  69. @Bean
  70. public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
  71. AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
  72. advisor.setSecurityManager(securityManager);
  73. return advisor;
  74. }
  75. }

MyRealm

  1. import org.apache.log4j.LogManager;
  2. import org.apache.log4j.Logger;
  3. import org.apache.shiro.authc.*;
  4. import org.apache.shiro.authz.AuthorizationInfo;
  5. import org.apache.shiro.authz.SimpleAuthorizationInfo;
  6. import org.apache.shiro.realm.AuthorizingRealm;
  7. import org.apache.shiro.subject.PrincipalCollection;
  8. import org.inlighting.database.ShiroUserBean;
  9. import org.inlighting.database.UserService;
  10. import org.inlighting.util.JWTUtil;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.stereotype.Service;
  13. import java.util.Arrays;
  14. import java.util.HashSet;
  15. @Service
  16. public class MyRealm extends AuthorizingRealm {
  17. private static final Logger LOGGER = LogManager.getLogger(MyRealm.class);
  18. private UserService userService;
  19. @Autowired
  20. public void setUserService(UserService userService) {
  21. this.userService = userService;
  22. }
  23. /**
  24. * 必须重写此方法,不然Shiro会报错
  25. */
  26. @Override
  27. public boolean supports(AuthenticationToken token) {
  28. return token != null;
  29. }
  30. /**
  31. * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
  32. */
  33. @Override
  34. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  35. ShiroUserBean shiroUserBean = JWTUtil.getUserBean(principals.toString());
  36. SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
  37. simpleAuthorizationInfo.addRoles(new HashSet<>(Arrays.asList(shiroUserBean.getRole().split(","))));
  38. simpleAuthorizationInfo.addStringPermissions(new HashSet<>(Arrays.asList(shiroUserBean.getPermission().split(","))));
  39. return simpleAuthorizationInfo;
  40. }
  41. /**
  42. * 校验是否需要登录
  43. */
  44. @Override
  45. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
  46. String token = (String) auth.getCredentials();
  47. if (! JWTUtil.verify(token)) {
  48. throw new AuthenticationException("need login");
  49. }
  50. return new SimpleAuthenticationInfo(token, token, "my_realm");
  51. }
  52. }

测试

  1. import org.apache.log4j.LogManager;
  2. import org.apache.log4j.Logger;
  3. import org.apache.shiro.SecurityUtils;
  4. import org.apache.shiro.authz.annotation.*;
  5. import org.apache.shiro.subject.Subject;
  6. import org.inlighting.bean.Result;
  7. import org.inlighting.database.ShiroUserBean;
  8. import org.inlighting.database.UserService;
  9. import org.inlighting.exception.UnauthorizedException;
  10. import org.inlighting.util.JWTUtil;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.http.HttpStatus;
  13. import org.springframework.web.bind.annotation.*;
  14. @RestController
  15. public class WebController {
  16. private static final Logger LOGGER = LogManager.getLogger(WebController.class);
  17. private UserService userService;
  18. @Autowired
  19. public void setService(UserService userService) {
  20. this.userService = userService;
  21. }
  22. @PostMapping("/login")
  23. public Result login(@RequestParam("username") String username,
  24. @RequestParam("password") String password) {
  25. ShiroUserBean shiroUserBean = userService.getUser(username);
  26. if (shiroUserBean.getPassword().equals(password)) {
  27. return Result.success(200, "Login success", JWTUtil.sign(shiroUserBean));
  28. } else {
  29. throw new UnauthorizedException();
  30. }
  31. }
  32. @GetMapping("/article")
  33. public Result article() {
  34. Subject subject = SecurityUtils.getSubject();
  35. if (subject.isAuthenticated()) {
  36. return Result.success(200, "You are already logged in", null);
  37. } else {
  38. return Result.success(200, "You are guest", null);
  39. }
  40. }
  41. @GetMapping("/require_auth")
  42. @RequiresAuthentication
  43. public Result requireAuth() {
  44. return Result.success(200, "You are authenticated", null);
  45. }
  46. @GetMapping("/require_role")
  47. @RequiresRoles("admin")
  48. public Result requireRole() {
  49. return Result.success(200, "You are visiting require_role", null);
  50. }
  51. @GetMapping("/require_permission")
  52. @RequiresPermissions(logical = Logical.AND, value = {"view", "edit"})
  53. public Result requirePermission() {
  54. return Result.success(200, "You are visiting permission require edit,view", null);
  55. }
  56. @RequestMapping(path = "/401")
  57. @ResponseStatus(HttpStatus.UNAUTHORIZED)
  58. public Result unauthorized() {
  59. return Result.error(401, "Unauthorized", null);
  60. }
  61. }

参考文章:

https://juejin.im/post/59f1b2766fb9a0450e755993