springboot

您所在的位置:网站首页 springboot前后端分离登录 springboot

springboot

2024-03-02 16:15| 来源: 网络整理| 查看: 265

springboot-shiro-jwt-redis实现用户登录的认证与授权(前后端分离) shiro-jwt-redis实现用户认证、授权大致流程

认证时进行缓存获取数据,否则进入认证方法(可以自己debug弄清流程更好)

 

相关依赖:

主要依赖:

  org.crazycake   shiro-redis-spring-boot-starter   3.2.1   com.auth0   java-jwt   3.10.3

代码中使用的工具依赖:

  com.alibaba   fastjson   1.2.35

避免启动失败:

在META-INF下创建spring-devtoos.propertis配置文件在里面编写

restart.include.shiro-redis=/shiro-[\\w-\\.]+jar

application.yml配置

# DataSource Config spring: datasource:   driver-class-name: com.mysql.cj.jdbc.Driver   url: jdbc:mysql://localhost:3306/vueblog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai   username: root   password: 123456 mybatis-plus: mapper-locations: classpath*:/mapper/**Mapper.xml server: port: 8081 logging: #sql日志 level:   com.xiaoke.mapper: DEBUG #dao接口全限定名称 shiro-redis: enabled: true redis-manager:   host: 127.0.0.1:6379

JwtUtils :生成token,以及判断token进行封装的工具包

package com.xiaoke.util.model;//写自己的包 ​ import com.alibaba.fastjson.JSONObject; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.interfaces.DecodedJWT; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; ​ import java.io.UnsupportedEncodingException; import java.util.Date; import java.util.HashMap; import java.util.Map; ​ // jwt工具类 ​ ​ @Slf4j @Data //自动生成setter、getter方法,可以直接写 @Component public class JwtUtils {   //token有效时长   private static final long EXPIRE=30*60*1000L;   //token的密钥   private static final String SECRET="jwt+shiro"; ​ ​   public static String createToken(JSONObject user) throws UnsupportedEncodingException {       //token过期时间       Date date=new Date(System.currentTimeMillis()+EXPIRE); ​       //jwt的header部分       Map map=new HashMap();       map.put("alg","HS256");       map.put("typ","JWT"); ​       //使用jwt的api生成token       String token= JWT.create()               .withHeader(map)               .withClaim("username", user.getString("username"))//私有声明               .withExpiresAt(date)//过期时间               .withIssuedAt(new Date())//签发时间               .sign(Algorithm.HMAC256(SECRET));//签名       return token;   } ​   //校验token的有效性,1、token的header和payload是否没改过;2、没有过期   public static boolean verify(String token){       try {           //解密           JWTVerifier verifier=JWT.require(Algorithm.HMAC256(SECRET)).build();           verifier.verify(token);           return true;       }catch (Exception e){           return false;       }   } ​ ​   //无需解密也可以获取token的信息   public static String getUsername(String token){       try {           DecodedJWT jwt = JWT.decode(token);           return jwt.getClaim("username").asString();       } catch (JWTDecodeException e) {           return null;       } ​   } }

JwtToken:用来创建token信息

package com.xiaoke.config.shiro; ​ import org.apache.shiro.authc.AuthenticationToken; ​ public class JwtToken implements AuthenticationToken {   private String token; ​   public JwtToken(String jwt){       this.token = jwt;   } ​   @Override   public Object getPrincipal() {       return token;   } ​   @Override   public Object getCredentials() {       return token;   } }

JwtFilter:用来处理拦截后的请求

package com.xiaoke.config.shiro; ​ import com.baomidou.mybatisplus.core.toolkit.StringUtils; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.web.filter.authc.AuthenticatingFilter; import org.springframework.stereotype.Component; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; ​ @Component public class JwtFilter extends AuthenticatingFilter { ​   @Override//重写token的创建,在执行onAccessDenied中的executeLogin时调用   protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {       HttpServletRequest request = (HttpServletRequest) servletRequest;       String jwt = request.getHeader("Authorization");       if(StringUtils.isEmpty(jwt)){           return null;       }       return new JwtToken(jwt);   } ​   @Override//onPreHandler中调用判断执行   protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {       System.out.println("ServletRequest");       HttpServletRequest request= (HttpServletRequest) servletRequest;       String token=request.getHeader("Authorization");       if(StringUtils.isEmpty(token)) {//mybatis-plus中工具包,也可以自行判断           System.out.println("ServletRequest::true");           return true;       }else {           try {               executeLogin(servletRequest, servletResponse);//调用Subject中login               return true;           }catch (Exception e){ ​               return false;           }       }   } ​ }

AccountProfile:用来处理redis必须获取id做key

package com.xiaoke.config.shiro; ​ import lombok.Data; ​ import java.io.Serializable; ​ @Data public class AccountProfile implements Serializable {   private Long id; ​   private String username; ​   private String email; ​   public AccountProfile() {   } ​   public AccountProfile(Long id, String username, String email) {       this.id = id;       this.username = username;       this.email = email;   } }

AccountRealm:进行认证、授权的自定义重写

package com.xiaoke.config.shiro; ​ import com.alibaba.fastjson.JSONObject; import com.xiaoke.service.PermissionService; import com.xiaoke.service.UserService; import com.xiaoke.util.model.JwtUtils; import org.apache.shiro.authc.*; 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.stereotype.Component; ​ import javax.annotation.Resource; import java.util.List; ​ @Component public class AccountRealm extends AuthorizingRealm {   @Resource   JwtUtils jwtUtils;   @Resource   UserService userService;   @Resource   PermissionService permissionService;   //为了让realm支持jwt的凭证校验   @Override   public boolean supports(AuthenticationToken token) {       return token instanceof JwtToken;   } ​   //权限校验   @Override//传过来的principals就是认证时候传的   protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {       System.out.println("授权~~~~~");       AccountProfile profile = (AccountProfile) principals.getPrimaryPrincipal();       String username = profile.getUsername();//拿到认证中所使用的principals来获取信息       List roles = permissionService.getRoles(username);       List permissions;       if(roles.get(0).containsValue("管理员")){//默认每一个人至少有一个角色           permissions = permissionService.getAllPermission();       }else {           permissions = permissionService.getPermission(username);       }       SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();       //查询数据库来获取用户的角色       for(JSONObject role: roles){           info.addRole(role.getString("role_name"));       }       //查询数据库来获取用户的权限       for(JSONObject permission: permissions){           info.addStringPermission(permission.getString("permission_code"));       }       return info;   }   //登录认证校验   @Override   protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {       String jwtToken = (String) token.getPrincipal();       String username = null;       try {             username = jwtUtils.getUsername(jwtToken);       }catch (Exception e){           throw new AuthenticationException("token非法,不是规范的token,可能被篡改了,或者过期了");       }       if (!jwtUtils.verify(jwtToken)||username==null){           throw new AuthenticationException("token认证失效,token错误或者过期,重新登陆");       }       //获取用户内容,判断用户是否存在###       JSONObject user = userService.findUserByName(username);       if(user == null){           throw new UnknownAccountException("账户不存在");       }       if(user.getString("status").equals(-1)){           throw new LockedAccountException("账户被锁定");       }       AccountProfile profile = new AccountProfile(user.getLong("id")                       ,user.getString("username"),user.getString("email"));       //BeanUtil.copyProperties(user,profile);//将user数据转移到profile       //原先这里SimpleAuthenticationInfo构造的时候传入的是username,       // 而redis做缓存是需要key,value的,这里必须要传入user,获取id做key.       //相应的授权方法中获取身份信息也要获取user           return new SimpleAuthenticationInfo(profile,jwtToken,getName());             //加盐处理,这个地方使用redis因为ByteSource没有实现Serializable接口           //需要自己定义重写定义序列化         //return new SimpleAuthenticationInfo(profile, ByteSource.Util.bytes("盐值"),getName());   } }

ShiroConfig:shiro过滤器的配置

package com.xiaoke.config.shiro; ​ import com.xiaoke.config.shiro.AccountRealm; import com.xiaoke.config.shiro.JwtFilter; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; import org.apache.shiro.mgt.DefaultSubjectDAO; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.Realm; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition; import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.crazycake.shiro.RedisCacheManager; import org.crazycake.shiro.RedisSessionDAO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import javax.annotation.Resource; import javax.servlet.Filter; ​ ​ ​ @Configuration public class ShiroConfig {   @Autowired   private JwtFilter jwtFilter; ​   @Bean   @Resource   public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {       DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();       sessionManager.setSessionDAO(redisSessionDAO);       return sessionManager;   }   @Bean   @Resource   public DefaultWebSecurityManager securityManager(Realm realm,                                                     SessionManager sessionManager,                                                     RedisCacheManager redisCacheManager) {       DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(realm);       redisCacheManager.setPrincipalIdFieldName("id");       securityManager.setSessionManager(sessionManager);       securityManager.setCacheManager(redisCacheManager); ​       //关闭shiro自带的session,之后采用token的形式来保存数据       DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();       DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();       defaultSessionStorageEvaluator.setSessionStorageEnabled(false);       subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);       securityManager.setSubjectDAO(subjectDAO);       return securityManager;   }   @Bean   public ShiroFilterChainDefinition shiroFilterChainDefinition() {       DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();       Map filterMap = new LinkedHashMap();       filterMap.put("/**", "jwt"); // 主要通过注解方式校验权限       chainDefinition.addPathDefinitions(filterMap);       return chainDefinition;   }   @Bean("shiroFilterFactoryBean")   public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,                                                         ShiroFilterChainDefinition shiroFilterChainDefinition) {       ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();       shiroFilter.setSecurityManager(securityManager); ​       Map filters = new HashMap();       filters.put("jwt", jwtFilter);//配置自定义过滤器       shiroFilter.setFilters(filters); ​ ​       Map filterMap = shiroFilterChainDefinition.getFilterChainMap();//配置拦截请求       //设置后所有请求通过jwt认证       shiroFilter.setFilterChainDefinitionMap(filterMap); ​       return shiroFilter;   }   @Bean(name = "realm")   public Realm getRealm(){       AccountRealm accountRealm = new AccountRealm();       accountRealm.setCachingEnabled(true);       //启用身份验证缓存,即缓存AuthenticationInfo信息,默认false       accountRealm.setAuthenticationCachingEnabled(true);       //缓存AuthenticationInfo信息的缓存名称 在ehcache-shiro.xml中有对应缓存的配置       accountRealm.setAuthenticationCacheName("authenticationCache");       //启用授权缓存,即缓存AuthorizationInfo信息,默认false       accountRealm.setAuthorizationCachingEnabled(true);       //缓存AuthorizationInfo信息的缓存名称 在ehcache-shiro.xml中有对应缓存的配置       accountRealm.setAuthorizationCacheName("authorizationCache");       /* //修改凭证校验匹配器       HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();       //设置加密算法为MD5       hashedCredentialsMatcher.setHashAlgorithmName("MD5");       //设置散列次数       hashedCredentialsMatcher.setHashIterations(1024);       accountRealm.setCredentialsMatcher(hashedCredentialsMatcher); */       return accountRealm;   } ​ }

UserController:user控制器(可自行编写,只做参考)

package com.xiaoke.controller; ​ ​ import com.alibaba.fastjson.JSONObject; import com.xiaoke.entity.User; import com.xiaoke.mapper.PermissionMapper; import com.xiaoke.service.UserService; import com.xiaoke.util.contants.ErrorEnum; import com.xiaoke.util.model.JwtUtils; import com.xiaoke.util.model.Result; import org.apache.shiro.authz.annotation.RequiresAuthentication; import org.apache.shiro.authz.annotation.RequiresRoles; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.UnsupportedEncodingException; import java.util.List; ​ /** *

* 前端控制器 *

* * @author anonymous * @since 2022-04-18 */ @RestController @RequestMapping("/user") public class UserController {   @Resource   private UserService userService; ​   @Resource   private PermissionMapper permissionMapper; ​   @RequiresAuthentication   @RequiresRoles("管理员")   @GetMapping("/index")   public JSONObject index(){       return userService.getById(1);   }   @GetMapping("/Login")   public JSONObject login(User user, HttpServletResponse response) throws UnsupportedEncodingException {       JSONObject userDb = userService.findUserByName(user.getUsername());       if(userDb!=null){           if(userDb.getString("password").equals(user.getPassword())){               String token = JwtUtils.createToken(userDb);               response.setHeader("Authorization",token);               return Result.successLogin();           }           return Result.errorJson(ErrorEnum.E_password);       }       return Result.errorJson(ErrorEnum.E_username);//Result为返回状态信息,可自行编写   }   @RequiresAuthentication   @GetMapping("/getUser")   public JSONObject getUser(HttpServletRequest request,HttpServletResponse response){       JSONObject data = new JSONObject();       String token = request.getHeader("Authorization");       String username = JwtUtils.getUsername(token);       List roles = permissionMapper.getRoles(username);       List permission;       data.put("roles",roles);       if(roles.get(0).containsValue("管理员")){           permission = permissionMapper.getAllPermission();       }else {           permission = permissionMapper.getPermission(username);       }       data.put("permission",permission);       response.setHeader("Authorization",token);       return Result.successJson(data);   }   @GetMapping("/register")   public JSONObject register(User user){       return userService.register(user);   } ​ }

GlobalExceptionHandler:全局异常处理(可自行编写,只做参考)

package com.xiaoke.common.exception; ​ import com.alibaba.fastjson.JSONObject; import com.auth0.jwt.exceptions.TokenExpiredException; import com.xiaoke.util.contants.ErrorEnum; import com.xiaoke.util.model.Result; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.ShiroException; import org.apache.shiro.authz.UnauthenticatedException; import org.apache.shiro.authz.UnauthorizedException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; ​ import java.io.IOException; ​ /** * 日志输出 * 全局捕获异常 */ @Slf4j @RestControllerAdvice public class GlobalExceptionHandler {   @ResponseStatus(HttpStatus.UNAUTHORIZED) //因为前后端分离 返回一个状态 一般是401 没有权限   @ExceptionHandler(value = ShiroException.class)//捕获运行时异常ShiroException是大部分异常的父类   public JSONObject handler(ShiroException e){       log.error("运行时异常:-----------------{}",e);       return Result.errorJson(e.getMessage());   } ​ ​   @ResponseStatus(HttpStatus.BAD_REQUEST) //因为前后端分离 返回一个状态   @ExceptionHandler(value = RuntimeException.class)//捕获运行时异常   public JSONObject handler(RuntimeException e){       log.error("运行时异常:-----------------{}",e);       return Result.errorJson(e.getMessage());   } ​   // 捕捉shiro的异常   @ResponseStatus(HttpStatus.UNAUTHORIZED)   @ExceptionHandler(UnauthenticatedException.class)   public JSONObject handle401(UnauthenticatedException e) {       return Result.errorJson(ErrorEnum.E_20011);   }   @ResponseStatus(HttpStatus.UNAUTHORIZED)   @ExceptionHandler(UnauthorizedException.class)   public JSONObject handle(UnauthorizedException e) {       return Result.errorJson(ErrorEnum.E_无权限);   }   }

以下可自行编写,仅作参考:(可以自行编写)

Result:返回信息进行封装

package com.xiaoke.util.model; ​ import com.alibaba.fastjson.JSONObject; import com.xiaoke.util.contants.Contants; import com.xiaoke.util.contants.ErrorEnum; ​ public class Result {   public static JSONObject successJson(Object data){       JSONObject resultJson = new JSONObject();       resultJson.put("code", Contants.SUCCESS_CODE);       resultJson.put("msg",Contants.SUCCESS_MSG);       resultJson.put("data",data);       return resultJson;   } ​   public static JSONObject successLogin(){       JSONObject resultJson = new JSONObject();       resultJson.put("code", Contants.SUCCESS_CODE);       resultJson.put("msg","登录成功");       resultJson.put("data", new JSONObject());       return resultJson;   }   public static JSONObject errorJson(ErrorEnum errorEnum){       JSONObject resultJson = new JSONObject();       resultJson.put("code", errorEnum.getErrorCode());       resultJson.put("msg", errorEnum.getErrorMsg());       resultJson.put("data", new JSONObject());       return resultJson;   }   public static JSONObject errorJson(String errorEnum){       JSONObject resultJson = new JSONObject();       resultJson.put("code", 401);       resultJson.put("msg", errorEnum);       resultJson.put("data", new JSONObject());       return resultJson;   } } package com.xiaoke.util.contants; ​ //通用常量方便管理 public class Contants {   public static final String SUCCESS_CODE = "200";   public static final String SUCCESS_MSG = "请求成功"; } package com.xiaoke.util.contants; ​ /** */ public enum ErrorEnum {   /*   * 错误信息   * */     E_10010("10010","登录失败,请重新登录"),   E_20011("401", "登陆已过期,请重新登陆"),   E_username("400","用户名错误"),   E_password("400","密码错误"), ​   private String errorCode; ​   private String errorMsg; ​   ErrorEnum(String errorCode, String errorMsg) {     this.errorCode = errorCode;     this.errorMsg = errorMsg;   } ​   public String getErrorCode() {     return errorCode;   } ​   public String getErrorMsg() {     return errorMsg;   } ​ }

如果shiro中要进行密码加盐处理:开启ShiroConfig中校验匹配器,在redis序列化会出现问题可以看以下进行处理:

(75条消息) springBoot+shiro+redis缓存实现时,反序列化的一个错误no valid constructor_Eden4J的博客-CSDN博客

在运行时可能出现的一些问题:

1.redis服务没有开启

2.application.yml配置中数据库信息错误

3.相关依赖没有添加完成

以上是本人花费一些时间查找相关资料以及debug之后的总结,如果有更好的方法或者建议,欢迎互相学习



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3