素材巴巴 > 程序开发 >

shiro springboot jwt redis

程序开发 2023-09-07 22:10:36

shiro

三大组件 :

Subject:包含用户操作信息,
SecurityManager:shiro 的核心心脏,安全管理器,用户的登录校验
Realm:数据库用户信息,权限角色验证等

核心方法:

身份验证(getAuthenticationInfo 方法)验证账户和密码,并返回相关信息
权限获取(getAuthorizationInfo 方法) 获取指定身份的权限,并返回相关信息
令牌支持(supports方法)判断该令牌(Token)是否被支持,令牌有很多种类型,例如:HostAuthenticationToken(主机验证令牌),UsernamePasswordToken(账户密码验证令牌)
采用jwt+redis+shiro+springboot 通过 rbca 实现框架搭建
 注意:
 1. jwt本身就有超时时间为什么还用redis?jwt无法控制退出后清除token,导致退出后还能拿token登录,且没有请求延长token时效功能,所以交给redis控制,jwt仅作为工具使用redis存储方式 虽然违背了 jwt 使用原则,但是基于业务这是更好的选择。
 2. shiro 认证AuthenticationInfo 和授权AuthorizationInfo 抛出异常如何通过全局异常处理返回给前端?全局异常无法捕捉到shiro的异常,只能通过自己的BasicHttpAuthenticationFilter 捕捉进行处理,例如:	public class JwtFilter extends BasicHttpAuthenticationFilter{protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {try {........} catch (java.lang.Exception e) {this.response401(request,response,401,e.getCause().getMessage());}}......./*** 捕捉shiro 认证授权异常!!!,全局异常无法捕捉,只能从这里捕捉 全部 401*/private void response401(ServletRequest req, ServletResponse resp,int code,String msg) {try {HttpServletResponse response = (HttpServletResponse) resp;response.setHeader("Context-type", "application/json;charset=UTF-8");response.setCharacterEncoding("UTF-8");JSONObject jsonObject = new JSONObject();jsonObject.put("data","");jsonObject.put("code",code);jsonObject.put("msg",msg);PrintWriter out = response.getWriter();out.print(jsonObject.toJSONString());out.flush();out.close();} catch (java.lang.Exception e) {}}}
 3. redis 可以在每次请求都给token重置超时,但在filter里无法注入redisTemplate?因为生命周期 filter --> servlet 所以在servlet之前,无法将redis注入到容器,只能写好工具类主动获取:RedisUtil redisUtil = SpringContextUtil.getBean(RedisUtil.class);
 4. 每次请求都走 shiro 认证授权,角色权限都要查库,耗费资源,可以缓存到redis

结构
在这里插入图片描述

pomorg.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-testcom.alibabadruid-spring-boot-starter1.1.10mysqlmysql-connector-javaruntimecom.baomidoumybatis-plus-boot-starter3.3.2com.baomidoumybatis-plus-generator3.4.1org.apache.velocityvelocity-engine-core2.2org.apache.shiroshiro-core1.4.0org.apache.shiroshiro-spring1.4.0org.apache.shiroshiro-ehcache1.4.0com.auth0java-jwt3.2.0org.projectlomboklombokio.jsonwebtokenjjwt0.9.0io.springfoxspringfox-swagger22.9.2com.github.xiaoyminswagger-bootstrap-ui1.9.6commons-langcommons-lang2.6org.aspectjaspectjweaver1.9.7joda-timejoda-timeorg.apache.commonscommons-lang33.9org.springframework.bootspring-boot-starter-data-rediscom.alibabafastjson1.2.28
 
yml
 server:port: 8080
 spring:datasource:url: jdbc:mysql://localhost:3307/shiro?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTCusername: rootpassword: phptsdriver-class-name: com.mysql.jdbc.Driverjackson:time-zone: Asia/Shanghairedis:jedis:pool:#最大连接数max-active: 8#最大阻塞等待时间(负数表示没限制)max-wait: -1#最大空闲max-idle: 8#最小空闲min-idle: 0#连接超时时间timeout: 10000
 token:# 在redis中有效时长(分)life: 10# 生成token 秘钥secret: 123
 
包configs -->mybatisplus
 @Configuration
 public class MybatisPlusConfig {/*** 分页插件*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();//分页插件interceptor.addInnerInterceptor(new PaginationInnerInterceptor());//针对 update和 delete语句,作用: 阻止恶意的全表更新删除interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());return interceptor;}
 }@Component
 public class MyBatisPlusFillTime implements MetaObjectHandler {@Overridepublic void insertFill(MetaObject metaObject) {//插入时间this.setFieldValByName("createTime", new Date(), metaObject);this.setFieldValByName("updateTime", new Date(), metaObject);}@Overridepublic void updateFill(MetaObject metaObject) {//更新时间this.setFieldValByName("updateTime", new Date(), metaObject);}
 }
 ---------------------------------
 包configs -->RedisConfig
 @Configuration
 @EnableCaching
 public class RedisConfig extends CachingConfigurerSupport {@Bean(name = "redisTemplate")public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){RedisTemplate redisTemplate = new RedisTemplate<>();//参照StringRedisTemplate内部实现指定序列化器redisTemplate.setConnectionFactory(redisConnectionFactory);redisTemplate.setKeySerializer(keySerializer());redisTemplate.setHashKeySerializer(keySerializer());redisTemplate.setValueSerializer(valueSerializer());redisTemplate.setHashValueSerializer(valueSerializer());return redisTemplate;}private RedisSerializer keySerializer(){return new StringRedisSerializer();}//使用Jackson序列化器private RedisSerializer valueSerializer(){return new GenericJackson2JsonRedisSerializer();}
 }
 ---------------------------------
 包configs -->shiro
 @Configuration
 public class ShiroConfig {@Bean("getJwtRealm")public JwtRealm getJwtRealm(){return new JwtRealm();}@Bean("securityManager")public DefaultWebSecurityManager securityManager(@Qualifier("getJwtRealm") JwtRealm jwtRealm){DefaultWebSecurityManager manager = new DefaultWebSecurityManager();manager.setRealm(jwtRealm);//关闭shiro自带sessionDefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();defaultSessionStorageEvaluator.setSessionStorageEnabled(false);subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);manager.setSubjectDAO(subjectDAO);return manager;}@Bean("shiroFilterFactoryBean")public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();//过滤Map filters = new HashMap<>();filters.put("jwt", new JwtFilter());factoryBean.setFilters(filters);Map filterMap = new LinkedHashMap<>();filterMap.put("/doc.html","anon");filterMap.put("/webjars/**", "anon");filterMap.put("/druid/**", "anon");filterMap.put("/app/**", "anon");//互挤模式登录filterMap.put("/sys/login", "anon");//共享模式登录filterMap.put("/sys/loginMany", "anon");filterMap.put("/swagger/**", "anon");filterMap.put("/v2/api-docs", "anon");filterMap.put("/swagger-ui.html", "anon");filterMap.put("/swagger-resources/**", "anon");filterMap.put("/captcha", "anon");filterMap.put("/aaa.txt", "anon");filterMap.put("/**", "jwt");factoryBean.setFilterChainDefinitionMap(filterMap);factoryBean.setSecurityManager(securityManager);return factoryBean;}//禁用session,不保存用户登录状态@Beanprotected SessionStorageEvaluator sessionStorageEvaluator(){DefaultWebSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();sessionStorageEvaluator.setSessionStorageEnabled(false);return sessionStorageEvaluator;}@Beanpublic DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();autoProxyCreator.setProxyTargetClass(true);return autoProxyCreator;}@Beanpublic LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){return new LifecycleBeanPostProcessor();}/*** 开启shiro aop注解支持.* 使用代理方式;所以需要开启代码支持;** @param* @return*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("getJwtRealm") JwtRealm jwtRealm) {SecurityManager manager = securityManager(jwtRealm);AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();advisor.setSecurityManager(manager);return advisor;}
 }
 ---------------------------------
 包configs -->swagger
 @Configuration
 @EnableSwagger2
 public class Swagger2Config {@Beanpublic Docket createRestApi(){return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.basePackage("com.shirojwt.demo.controller")).paths(PathSelectors.any()).build().securityContexts(securityContexts()).securitySchemes(securitySchemes());}private List securityContexts() {List res = new ArrayList<>();// 设置需要登录认证的路径res.add(getContextByPath("/hello/*"));return res;}private SecurityContext getContextByPath(String pathRegex) {return SecurityContext.builder().securityReferences(defaultAuthPath()).forPaths(PathSelectors.regex(pathRegex)).build();}private List defaultAuthPath() {List res = new ArrayList<>();AuthorizationScope scope = new AuthorizationScope("global", "accessEverything");AuthorizationScope[] scopes = new AuthorizationScope[1];scopes[0] = scope;res.add(new SecurityReference("Authorization",scopes));return res;}private List securitySchemes() {List res = new ArrayList<>();// 设置请求头信息ApiKey apiKey = new ApiKey("Auth", "Authorization", "Header");res.add(apiKey);return res;}private ApiInfo apiInfo() {return new ApiInfoBuilder().title("springboot+shiro+jwt框架接口文档").description("springboot+shiro+jwt框架").contact(new Contact("john", "http://localhost:8080/doc.html","11111111@qq.com")).version("1.0.0").build();}}
 ---------------------------------
 包configs -->token
 /*** Created with IntelliJ IDEA* Time: 15:40* 读取配置文件*/
 @Data
 @Component
 @ConfigurationProperties(prefix = "token")
 public class TokenLife {/*** redis token 时长  (分)*/private Integer life;/*** 生成token秘钥*/private String secret;
 }
 ---------------------------------
 包configs -->web
 @Configuration
 public class CrossOriginConfig {@Beanpublic CorsFilter corsFilter(){CorsConfiguration config =new CorsConfiguration();//允许跨越发送cookieconfig.addAllowedOrigin("*");config.addAllowedOrigin("null");//放行全部原始头信息config.setAllowCredentials(true);//允许所有请求方法跨域调用config.addAllowedHeader("*");config.addAllowedMethod("*");UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", config);return new CorsFilter(source);}
 }
 ---------------------------------
 包utils -->exception
 /*** 统一返回的异常信息的格式** @author jl**/
 @Component
 public class ExceptionResponseEntity {@Getterprivate int code;@Getterprivate String message;public ExceptionResponseEntity() {}public ExceptionResponseEntity(int code, String message) {this.code = code;this.message = message;}
 }/*** Created with IntelliJ IDEA* Date: 2022/11/8* Time: 17:45*/
 @RestControllerAdvice
 @Slf4j
 public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {/*** 如果需要捕获多个异常   定义如下:@ExceptionHandler({})** @param request* @param e* @param response* @return*/// 捕获多个异常的写法@ExceptionHandler({MyCustomException.class,MyCustomException.class})public R customExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());MyCustomException exception = (MyCustomException) e;log.error("全局异常信息 ex={}", e.getMessage(), e);return R.fail(exception.getCode(), e.getMessage());}/***  捕获  RuntimeException 异常*  如果在一个 exceptionHandler 通过  if (e instanceof xxxException) 太麻烦,*  可以写多个方法标注@ExceptionHandler处理不同的异常** @param e        exception* @param response response* @return 响应结果*/@ExceptionHandler(RuntimeException.class)public R runtimeExceptionHandler(final Exception e, HttpServletResponse response) {response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());if(e.getMessage().contains("com.mysql")){log.info("nn SqlException===========  Exception Start  ===== n"+e+"n "+"SqlException===========  Exception End  =====nn");}else {log.info("nn RuntimeException===========  Exception Start  ===== n"+e+"n "+"RuntimeException===========  Exception End  =====nn");}return R.fail(HttpStatusCode.ERROR.getCode(),e.getMessage());}
 }/*** Created with IntelliJ IDEA* Date: 2022/11/8* Time: 17:41* 自定义异常*/
 @Component
 public class MyCustomException extends RuntimeException{private static final long serialVersionUID = 8863339790253662109L;@Getterprivate int code ;@Getterprivate String message;public MyCustomException() {super();}public MyCustomException(String message) {int code =500;this.message = message;this.code = code;}public MyCustomException(int code,String message) {this.message = message;this.code = code;}
 }---------------------------------
 包utils -->init
 /*** Created with IntelliJ IDEA* Time: 16:11* 初始化工具类,用于生命周期在servlet前还没有注入到容器中,通过工具主动获取配置类*/@Component
 public class SpringContextUtil implements ApplicationContextAware {private static ApplicationContext applicationContext = null;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {if(SpringContextUtil.applicationContext == null){SpringContextUtil.applicationContext  = applicationContext;}}public static ApplicationContext getApplicationContext() {return applicationContext;}public static Object getBean(String name){return getApplicationContext().getBean(name);}public static  T getBean(Class clazz){return getApplicationContext().getBean(clazz);}public static  T getBean(String name,Class clazz){return getApplicationContext().getBean(name, clazz);}
 }---------------------------------
 包utils -->jwt/*** Created with IntelliJ IDEA* Time: 17:49** 所有请求都要先走filter 进行校验是否携带token* 每次请求都走一遍*/
 public class JwtFilter extends BasicHttpAuthenticationFilter {/*** 校验是否携带token* 携带:true*/@Overrideprotected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {HttpServletRequest req = (HttpServletRequest) request;String authorization = req.getHeader(AUTHORIZATION_HEADER);System.out.println("===============filter获取请求头Authorization: "+authorization+"==============================");return authorization != null;}/*** 将token注入到 shiro 中当做 shiro 的 token*/@Overrideprotected boolean executeLogin(ServletRequest request, ServletResponse response) {HttpServletRequest httpServletRequest = (HttpServletRequest) request;String authorization = httpServletRequest.getHeader(AUTHORIZATION_HEADER);//token注入到shiro中JwtToken token = new JwtToken(authorization);// 通过shiro登录:此时会去realm(jwtRealm) 通过 AuthenticationInfo doGetAuthenticationInfo 认证getSubject(request, response).login(token);// 如果没有抛出异常则代表登入成功,返回true// 主动获取redis 和 yml配置文件,在filter生命周期,容器未注入redisRedisUtil redisUtil = SpringContextUtil.getBean(RedisUtil.class);TokenLife tokenLife = SpringContextUtil.getBean(TokenLife.class);//redis 续期redisUtil.expire(JwtTokenUtil.getUsername(authorization),tokenLife.getLife());return true;}/*** 认证或授权异常捕捉返回给前端*/@SneakyThrows@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {if (isLoginAttempt(request, response)) {try {executeLogin(request, response);} catch (java.lang.Exception e) {this.response401(request,response,401,e.getCause().getMessage());}}else {//未携带token , 直接抛异常this.response401(request,response,401,"权限不足,缺失token");}//此处仍然返回true 是因为 方法上已经加了 权限规则,即使访问了接口,但是没权限依然走不下去//或者没有token代表是游客访问return true;}/*** 对跨域提供支持*/@Overrideprotected boolean preHandle(ServletRequest request, ServletResponse response) throws java.lang.Exception {HttpServletRequest httpServletRequest = (HttpServletRequest) request;HttpServletResponse httpServletResponse = (HttpServletResponse) response;httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {httpServletResponse.setStatus(HttpStatus.OK.value());return false;}return super.preHandle(request, response);}/*** 捕捉shiro 认证授权异常!!!,全局异常无法捕捉,只能从这里捕捉 全部 401*/private void response401(ServletRequest req, ServletResponse resp,int code,String msg) {try {HttpServletResponse response = (HttpServletResponse) resp;response.setHeader("Context-type", "application/json;charset=UTF-8");response.setCharacterEncoding("UTF-8");JSONObject jsonObject = new JSONObject();jsonObject.put("data","");jsonObject.put("code",code);jsonObject.put("msg",msg);PrintWriter out = response.getWriter();out.print(jsonObject.toJSONString());out.flush();out.close();} catch (java.lang.Exception e) {}}
 }public class JwtRealm extends AuthorizingRealm {@Autowiredprivate UserInfoService userInfoService;@Autowiredprivate RoleAuthService roleAuthService;@Autowiredprivate AuthInfoService authInfoService;@Autowiredprivate RedisUtil redisUtil;@Autowiredprivate TokenLife tokenLife;@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof JwtToken;}/*** 授权* @param principalCollection* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("==================用户授权=============");//存放 角色 、权限信息SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();//1 获取用户信息UserInfo user = (UserInfo) principalCollection.getPrimaryPrincipal();UserInfo userInfo = userInfoService.findUserByUsername(user.getUsername());if(ObjectUtils.isEmpty(userInfo)){throw new MyCustomException(401,"用户不存在!");}//2 根据用户获取角色信息Integer roleId = user.getRoleId();Set roles = new HashSet<>();roles.add(String.valueOf(roleId));//3 根据角色信息获取权限信息Set authodKeys = new HashSet<>();// 3.1 去角色权限信息关联表去查所有的角色权限List allAuthorList = roleAuthService.list(new QueryWrapper().eq("role_id", roleId));if(CollectionUtils.isNotEmpty(allAuthorList)){// 3.2 去重的权限idList distinctAuthorId = allAuthorList.stream().map(e -> e.getAuthId()).collect(Collectors.toList());// 3.3 所有的权限信息List authInfoList = authInfoService.list(new QueryWrapper().in("id",distinctAuthorId ));if(CollectionUtils.isNotEmpty(authInfoList)){// 3.4 提取权限key: auth_keyauthodKeys = authInfoList.stream().map(e -> e.getAuthKey()).collect(Collectors.toSet());}}//4 角色授权info.setRoles(roles);//5 权限授权info.setStringPermissions(authodKeys);System.out.println("==================授权成功=============");return null;}/*** 认证* 过滤器拦截到请求信息* 根据token进行认证* @param authenticationToken* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {System.out.println("==================token认证=============");//获取前端界面传来的tokenString token = (String) authenticationToken.getPrincipal();if(StringUtils.isBlank(token)){throw new MyCustomException(401,"token不存在!");}//1 根据token 获取 usernameString username = JwtTokenUtil.getUsername(token);if(StringUtils.isBlank(username)){throw new MyCustomException(401,"token异常!");}//2 校验token格式 是否正确boolean verify = JwtTokenUtil.verify(token,  tokenLife.getSecret());if(!verify) {throw new MyCustomException(401,"token异常!");}//3 根据username 去redis 中查 token ,前端页面传入的token是否和 redis中的token一致if(!redisUtil.hasKey(username)){throw new MyCustomException(401,"token失效!");}else {if( !StringUtils.equals( token, redisUtil.get(username).toString()  ) ){throw new MyCustomException(401,"token不一致,校验失败!");}}UserInfo userInfo = userInfoService.findUserByUsername(username);if(ObjectUtils.isEmpty(userInfo)){throw new MyCustomException(401,"用户不存在!");}return new SimpleAuthenticationInfo(userInfo,token,getName());}}/*** 自定义token传给shiro认证* @Version 1.0.0*/
 public class JwtToken implements AuthenticationToken {// 密钥private String token;public JwtToken(String token) {this.token = token;}@Overridepublic Object getPrincipal() {return token;}@Overridepublic Object getCredentials() {return token;}}/*** Created with IntelliJ IDEA* Time: 10:39*/
 @Component
 public class JwtTokenUtil {//此处jwt 配合redis 使用,jwt只是生成token 和校验 token是否正确,不进行超时校验,超时放到redis中:redis//且每次请求都刷新redis 超时时间//认证时加一步 通过前端传来的token 获取用户的id,在通过用户id 去redis查,根据redis判断前端传的token 和redis存的token 是否一致,不一致那就是token失效了//作者private static final String ISSUSER = "john";// 签名的主题private static final String SUBJECT = "subject";// 签名的观众private static final String AUDIENCE = "web-Program";//过期时间12小时private static final long EXPIRE_TIME = 43200*1000;/*** 获得token中的信息无需secret解密也能获得* @return token中包含的用户名*/public static String getUsername(String token) {try {DecodedJWT jwt = JWT.decode(token);return jwt.getClaim("username").asString();} catch (JWTDecodeException e) {return null;}}/*** 根据token+secret 校验token是否正确* @param token 密钥* @return 是否正确*/public static boolean verify(String token, String secret) {try {DecodedJWT jwt1 = JWT.decode(token);String username = jwt1.getClaim("username").asString();Algorithm algorithm = Algorithm.HMAC256(secret);JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username ).build();DecodedJWT jwt = verifier.verify(token);return true;} catch (Exception exception) {return false;}}/*** 校验token是否正确* @param token 密钥* @return 是否正确*/public static boolean verify(String token, String username, String secret) {try {Algorithm algorithm = Algorithm.HMAC256(secret);JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username ).build();DecodedJWT jwt = verifier.verify(token);return true;} catch (Exception exception) {return false;}}/*** 生成签名,12小时后过期* @param username 用户名* @return 加密的token*/public static String creatToken(String username,String secret) {long now = System.currentTimeMillis();Date nowDate = new Date(now);Date expireDate = new Date(now+EXPIRE_TIME);try {Algorithm algorithm = Algorithm.HMAC256(secret);Map map = new HashMap();map.put("alg", "HS256");map.put("typ", "JWT");String token = JWT.create()// 设置头部信息 Header.withHeader(map)// 设置 载荷 Payload.withClaim("username", username).withClaim("testkey","value")// 发布者.withIssuer(ISSUSER)// 主题.withSubject(SUBJECT)//接受者.withAudience(AUDIENCE)// 生成签名的时间.withIssuedAt(nowDate)// 签名过期的时间.withExpiresAt(expireDate)// 签名 Signature.sign(algorithm);return token;} catch (JWTCreationException exception){exception.printStackTrace();} catch (UnsupportedEncodingException e) {e.printStackTrace();}return null;}public static boolean isExpire(String token){DecodedJWT jwt = JWT.decode(token);return System.currentTimeMillis() > jwt.getExpiresAt().getTime();}}--------------------------------------------
 包utils---->redis
 @Component
 public class RedisUtil {@Autowiredprivate RedisTemplate redisTemplate;public RedisUtil(RedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}/*** 指定缓存失效时间* @param key 键* @param time 时间(分)* @return*/public  boolean expire(String key,long time){try {if(time>0){redisTemplate.expire(key, time, TimeUnit.MINUTES);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 根据key 获取过期时间* @param key 键 不能为null* @return 时间(秒) 返回0代表为永久有效*/public long getExpire(String key){return redisTemplate.getExpire(key,TimeUnit.SECONDS);}/*** 判断key是否存在* @param key 键* @return true 存在 false不存在*/public boolean hasKey(String key){try {return redisTemplate.hasKey(key);} catch (Exception e) {e.printStackTrace();return false;}}/*** 删除缓存* @param key 可以传一个值 或多个*/@SuppressWarnings("unchecked")public void del(String ... key){if(key!=null&&key.length>0){if(key.length==1){redisTemplate.delete(key[0]);}else{redisTemplate.delete(CollectionUtils.arrayToList(key));}}}//============================String=============================/*** 普通缓存获取* @param key 键* @return 值*/public Object get(String key){return key==null?null:redisTemplate.opsForValue().get(key);}/*** 普通缓存放入* @param key 键* @param value 值* @return true成功 false失败*/public boolean set(String key,Object value) {try {redisTemplate.opsForValue().set(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 普通缓存放入并设置时间* @param key 键* @param value 值* @param time 时间(分钟) time要大于0 如果time小于等于0 将设置无限期* @return true成功 false 失败*/public boolean set(String key,Object value,long time){try {if(time>0){redisTemplate.opsForValue().set(key, value, time, TimeUnit.MINUTES);}else{set(key, value);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 递增* @param key 键* @param delta 要增加几(大于0)* @return*/public long incr(String key, long delta){if(delta<0){throw new RuntimeException("递增因子必须大于0");}return redisTemplate.opsForValue().increment(key, delta);}/*** 递减* @param key 键* @param delta 要减少几(小于0)* @return*/public long decr(String key, long delta){if(delta<0){throw new RuntimeException("递减因子必须大于0");}return redisTemplate.opsForValue().increment(key, -delta);}//================================Map=================================/*** HashGet* @param key 键 不能为null* @param item 项 不能为null* @return 值*/public Object hget(String key,String item){return redisTemplate.opsForHash().get(key, item);}/*** 获取hashKey对应的所有键值* @param key 键* @return 对应的多个键值*/public Map hmget(String key){return redisTemplate.opsForHash().entries(key);}/*** HashSet* @param key 键* @param map 对应多个键值* @return true 成功 false 失败*/public boolean hmset(String key, Map map){try {redisTemplate.opsForHash().putAll(key, map);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** HashSet 并设置时间* @param key 键* @param map 对应多个键值* @param time 时间(秒)* @return true成功 false失败*/public boolean hmset(String key, Map map, long time){try {redisTemplate.opsForHash().putAll(key, map);if(time>0){expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 向一张hash表中放入数据,如果不存在将创建* @param key 键* @param item 项* @param value 值* @return true 成功 false失败*/public boolean hset(String key,String item,Object value) {try {redisTemplate.opsForHash().put(key, item, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 向一张hash表中放入数据,如果不存在将创建* @param key 键* @param item 项* @param value 值* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间* @return true 成功 false失败*/public boolean hset(String key,String item,Object value,long time) {try {redisTemplate.opsForHash().put(key, item, value);if(time>0){expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 删除hash表中的值* @param key 键 不能为null* @param item 项 可以使多个 不能为null*/public void hdel(String key, Object... item){redisTemplate.opsForHash().delete(key,item);}/*** 判断hash表中是否有该项的值* @param key 键 不能为null* @param item 项 不能为null* @return true 存在 false不存在*/public boolean hHasKey(String key, String item){return redisTemplate.opsForHash().hasKey(key, item);}/*** hash递增 如果不存在,就会创建一个 并把新增后的值返回* @param key 键* @param item 项* @param by 要增加几(大于0)* @return*/public double hincr(String key, String item,double by){return redisTemplate.opsForHash().increment(key, item, by);}/*** hash递减* @param key 键* @param item 项* @param by 要减少记(小于0)* @return*/public double hdecr(String key, String item,double by){return redisTemplate.opsForHash().increment(key, item,-by);}//============================set=============================/*** 根据key获取Set中的所有值* @param key 键* @return*/public Set sGet(String key){try {return redisTemplate.opsForSet().members(key);} catch (Exception e) {e.printStackTrace();return null;}}/*** 根据value从一个set中查询,是否存在* @param key 键* @param value 值* @return true 存在 false不存在*/public boolean sHasKey(String key,Object value){try {return redisTemplate.opsForSet().isMember(key, value);} catch (Exception e) {e.printStackTrace();return false;}}/*** 将数据放入set缓存* @param key 键* @param values 值 可以是多个* @return 成功个数*/public long sSet(String key, Object...values) {try {return redisTemplate.opsForSet().add(key, values);} catch (Exception e) {e.printStackTrace();return 0;}}/*** 将set数据放入缓存* @param key 键* @param time 时间(秒)* @param values 值 可以是多个* @return 成功个数*/public long sSetAndTime(String key,long time,Object...values) {try {Long count = redisTemplate.opsForSet().add(key, values);if(time>0) {expire(key, time);}return count;} catch (Exception e) {e.printStackTrace();return 0;}}/*** 获取set缓存的长度* @param key 键* @return*/public long sGetSetSize(String key){try {return redisTemplate.opsForSet().size(key);} catch (Exception e) {e.printStackTrace();return 0;}}/*** 移除值为value的* @param key 键* @param values 值 可以是多个* @return 移除的个数*/public long setRemove(String key, Object ...values) {try {Long count = redisTemplate.opsForSet().remove(key, values);return count;} catch (Exception e) {e.printStackTrace();return 0;}}//===============================list=================================/*** 获取list缓存的内容* @param key 键* @param start 开始* @param end 结束 0 到 -1代表所有值* @return*/public List lGet(String key, long start, long end){try {return redisTemplate.opsForList().range(key, start, end);} catch (Exception e) {e.printStackTrace();return null;}}/*** 获取list缓存的长度* @param key 键* @return*/public long lGetListSize(String key){try {return redisTemplate.opsForList().size(key);} catch (Exception e) {e.printStackTrace();return 0;}}/*** 通过索引 获取list中的值* @param key 键* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推* @return*/public Object lGetIndex(String key,long index){try {return redisTemplate.opsForList().index(key, index);} catch (Exception e) {e.printStackTrace();return null;}}/*** 将list放入缓存* @param key 键* @param value 值* @return*/public boolean lSet(String key, Object value) {try {redisTemplate.opsForList().rightPush(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存* @param key 键* @param value 值* @param time 时间(秒)* @return*/public boolean lSet(String key, Object value, long time) {try {redisTemplate.opsForList().rightPush(key, value);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存* @param key 键* @param value 值* @return*/public boolean lSet(String key, List value) {try {redisTemplate.opsForList().rightPushAll(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存* @param key 键* @param value 值* @param time 时间(秒)* @return*/public boolean lSet(String key, List value, long time) {try {redisTemplate.opsForList().rightPushAll(key, value);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 根据索引修改list中的某条数据* @param key 键* @param index 索引* @param value 值* @return*/public boolean lUpdateIndex(String key, long index,Object value) {try {redisTemplate.opsForList().set(key, index, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 移除N个值为value* @param key 键* @param count 移除多少个* @param value 值* @return 移除的个数*/public long lRemove(String key,long count,Object value) {try {Long remove = redisTemplate.opsForList().remove(key, count, value);return remove;} catch (Exception e) {e.printStackTrace();return 0;}}/*** 模糊查询获取key值* @param pattern* @return*/public Set keys(String pattern){return redisTemplate.keys(pattern);}/*** 使用Redis的消息队列* @param channel* @param message 消息内容*/public void convertAndSend(String channel, Object message){redisTemplate.convertAndSend(channel,message);}/*** 根据起始结束序号遍历Redis中的list* @param listKey* @param start 起始序号* @param end 结束序号* @return*/public List rangeList(String listKey, long start, long end) {//绑定操作BoundListOperations boundValueOperations = redisTemplate.boundListOps(listKey);//查询数据return boundValueOperations.range(start, end);}/*** 弹出右边的值 --- 并且移除这个值* @param listKey*/public Object rifhtPop(String listKey){//绑定操作BoundListOperations boundValueOperations = redisTemplate.boundListOps(listKey);return boundValueOperations.rightPop();}
 }------------------------------
 包utils---->result
 public enum HttpStatusCode {CONTINUE(100, "Continue"),SWITCHING_PROTOCOLS(101, "Switching Protocols"),PROCESSING(102, "Processing"),CHECKPOINT(103, "Checkpoint"),OK(200, "OK"),CREATED(201, "Created"),ACCEPTED(202, "Accepted"),NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"),NO_CONTENT(204, "No Content"),RESET_CONTENT(205, "Reset Content"),PARTIAL_CONTENT(206, "Partial Content"),MULTI_STATUS(207, "Multi-Status"),ALREADY_REPORTED(208, "Already Reported"),IM_USED(226, "IM Used"),MULTIPLE_CHOICES(300, "Multiple Choices"),MOVED_PERMANENTLY(301, "Moved Permanently"),ERROR(500, "Bad Request"),FOUND(302, "Found");@Getterprivate int code;@Getterprivate String msg;HttpStatusCode(int code, String msg){this.code = code;this.msg = msg;}
 }@Data
 public class R {// 结果状态码private int code;// 响应信息private String msg;// 总条数private Long total;// 响应数据private T data;// 接口请求时间private long timestamp ;public R (){this.timestamp = System.currentTimeMillis();}public static  R success(T data){R R1 = new R();R1.setCode(HttpStatusCode.OK.getCode());R1.setMsg(HttpStatusCode.OK.getMsg());R1.setData(data);return  R1;}public static  R fail(int code, String msg){R R1 = new R();R1.setCode(code);R1.setMsg(msg);return R1;}public static  R fail(String msg){R R1 = new R();R1.setCode(500);R1.setMsg(msg);return R1;}
 }---------------------------
 包----->time
 public class DateUtils {/** 时间格式(yyyy-MM-dd) */public final static String DATE_PATTERN = "yyyy-MM-dd";/** 时间格式(yyyy-MM-dd HH:mm:ss) */public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";/*** 日期格式化 日期格式为:yyyy-MM-dd* @param date  日期* @return  返回yyyy-MM-dd格式日期*/public static String format(Date date) {return format(date, DATE_PATTERN);}/*** 日期格式化 日期格式为:yyyy-MM-dd* @param date  日期* @param pattern  格式,如:DateUtils.DATE_TIME_PATTERN* @return  返回yyyy-MM-dd格式日期*/public static String format(Date date, String pattern) {if(date != null){SimpleDateFormat df = new SimpleDateFormat(pattern);return df.format(date);}return null;}/*** 字符串转换成日期* @param strDate 日期字符串* @param pattern 日期的格式,如:DateUtils.DATE_TIME_PATTERN*/public static Date stringToDate(String strDate, String pattern) {if (StringUtils.isBlank(strDate)){return null;}DateTimeFormatter fmt = DateTimeFormat.forPattern(pattern);return fmt.parseLocalDateTime(strDate).toDate();}/*** 根据周数,获取开始日期、结束日期* @param week  周期  0本周,-1上周,-2上上周,1下周,2下下周* @return  返回date[0]开始日期、date[1]结束日期*/public static Date[] getWeekStartAndEnd(int week) {DateTime dateTime = new DateTime();LocalDate date = new LocalDate(dateTime.plusWeeks(week));date = date.dayOfWeek().withMinimumValue();Date beginDate = date.toDate();Date endDate = date.plusDays(6).toDate();return new Date[]{beginDate, endDate};}/*** 对日期的【秒】进行加/减** @param date 日期* @param seconds 秒数,负数为减* @return 加/减几秒后的日期*/public static Date addDateSeconds(Date date, int seconds) {DateTime dateTime = new DateTime(date);return dateTime.plusSeconds(seconds).toDate();}/*** 对日期的【分钟】进行加/减** @param date 日期* @param minutes 分钟数,负数为减* @return 加/减几分钟后的日期*/public static Date addDateMinutes(Date date, int minutes) {DateTime dateTime = new DateTime(date);return dateTime.plusMinutes(minutes).toDate();}/*** 对日期的【小时】进行加/减** @param date 日期* @param hours 小时数,负数为减* @return 加/减几小时后的日期*/public static Date addDateHours(Date date, int hours) {DateTime dateTime = new DateTime(date);return dateTime.plusHours(hours).toDate();}/*** 对日期的【天】进行加/减** @param date 日期* @param days 天数,负数为减* @return 加/减几天后的日期*/public static Date addDateDays(Date date, int days) {DateTime dateTime = new DateTime(date);return dateTime.plusDays(days).toDate();}/*** 对日期的【周】进行加/减** @param date 日期* @param weeks 周数,负数为减* @return 加/减几周后的日期*/public static Date addDateWeeks(Date date, int weeks) {DateTime dateTime = new DateTime(date);return dateTime.plusWeeks(weeks).toDate();}/*** 对日期的【月】进行加/减** @param date 日期* @param months 月数,负数为减* @return 加/减几月后的日期*/public static Date addDateMonths(Date date, int months) {DateTime dateTime = new DateTime(date);return dateTime.plusMonths(months).toDate();}/*** 对日期的【年】进行加/减** @param date 日期* @param years 年数,负数为减* @return 加/减几年后的日期*/public static Date addDateYears(Date date, int years) {DateTime dateTime = new DateTime(date);return dateTime.plusYears(years).toDate();}public static void main(String[] args) {System.out.println(addDateYears(new Date(),1));}
 } 
 
controller
 @Api(tags = "登录管理")
 @RestController
 public class LoginController {@Resourceprivate LoginService loginService;@ApiOperation(value = "用户登录(账号互相挤模式)")@PostMapping("/sys/login")public R login(@RequestBody UserInfo userInfo){return loginService.login(userInfo);}@ApiOperation(value = "用户登录(账号共享模式)")@PostMapping("/sys/loginMany")public R loginMany(@RequestBody UserInfo userInfo){return loginService.loginMany(userInfo);}
 }== servicepublic interface LoginService {/*** 登录成功并返回token* @return*/R login(UserInfo userInfo);/*** 多人登录成功并返回token* @return*/R loginMany(UserInfo userInfo);/*** 退出登录* @param userid* @return*/Object logout(Long userid);
 }== impl @Service
 public class LoginServiceImpl implements LoginService {//盐private final static String SALT = "123";@Autowiredprivate UserInfoService userService;@Autowiredprivate RedisUtil redisUtil;/*** 角色*/@Autowiredprivate RoleAuthService roleAuthService;/*** 权限*/@Autowiredprivate AuthInfoService authInfoService;/*** token 生命周期*/@Resourceprivate TokenLife tokenLife;@Overridepublic R login(UserInfo userInfo) {UserInfo user = userService.getOne(new QueryWrapper().eq("username", userInfo.getUsername()));//用户不存在if (user == null) {return R.fail("用户名或密码不正确!");}//密码 md5+盐String pwd = new SimpleHash("md5",userInfo.getPassword(),SALT,2).toHex();//校验密码是否相等if(!StringUtils.equals(pwd,user.getPassword())){return R.fail("用户名或密码不正确!");}//校验通过创建token 默认 12个小时超时(从内存中删除),但是控制时间还是在redis这,redis过期了  有token也没用了 得重新登陆String token = JwtTokenUtil.creatToken(user.getUsername(),tokenLife.getSecret());//删除redis中的tokenredisUtil.del(user.getUsername());//保存token 到redis,10min 超时redisUtil.set(user.getUsername(),token,tokenLife.getLife());user.setPassword(null);//获取登录用户角色权限信息,前端有可能需要Map roleAndPermission = this.getRoleAndPermission(user);return R.success(new HashMap(){{put("user", user);put("token", token);put("role", roleAndPermission.get("role"));put("permission",  roleAndPermission.get("permission"));}});}@Overridepublic R loginMany(UserInfo userInfo) {UserInfo user = userService.getOne(new QueryWrapper().eq("username", userInfo.getUsername()));//用户不存在if (user == null) {return R.fail("用户名或密码不正确!");}//密码 md5+盐,经过两次hashString pwd = new SimpleHash("md5",userInfo.getPassword(),SALT,2).toHex();//校验密码是否相等if(!StringUtils.equals(pwd,user.getPassword())){return R.fail("用户名或密码不正确!");}//校验通过创建token 默认 12个小时超时(从内存中删除),但是控制时间还是在redis这,redis过期了  有token也没用了 得重新登陆String token = null;//多人共享账号,redis存在就获取,不存在就创建if(redisUtil.hasKey(user.getUsername())){token = redisUtil.get(user.getUsername()).toString();}else {token =  JwtTokenUtil.creatToken(user.getUsername(),tokenLife.getSecret());//保存token 到redis,10min 超时redisUtil.set(user.getUsername(),token,tokenLife.getLife());}String finalToken = token;user.setPassword(null);//获取登录用户角色权限信息,前端有可能需要Map roleAndPermission = this.getRoleAndPermission(user);return R.success(new HashMap(){{put("user", user);put("token", finalToken);put("role", roleAndPermission.get("role"));put("permission",  roleAndPermission.get("permission"));}});}@Overridepublic Object logout(Long userid) {return null;}/*** 登录获取权限角色一并返给前端*/public  Map  getRoleAndPermission(UserInfo user) {//1 存放角色权限Map info = new HashMap<>();//2 根据用户获取角色信息Integer roleId = user.getRoleId();Set roles = new HashSet<>();roles.add(String.valueOf(roleId));//3 根据角色信息获取权限信息Set authodKeys = new HashSet<>();// 3.1 去角色权限信息关联表去查所有的角色权限List allAuthorList = roleAuthService.list(new QueryWrapper().eq("role_id", roleId));if(CollectionUtils.isNotEmpty(allAuthorList)){// 3.2 去重的权限idList distinctAuthorId = allAuthorList.stream().map(e -> e.getAuthId()).collect(Collectors.toList());// 3.3 所有的权限信息List authInfoList = authInfoService.list(new QueryWrapper().in("id",distinctAuthorId ));if(CollectionUtils.isNotEmpty(authInfoList)){// 3.4 提取权限key: auth_keyauthodKeys = authInfoList.stream().map(e -> e.getAuthKey()).collect(Collectors.toSet());}}//4 角色列表info.put("role",roles);//5 权限列表info.put("permission",authodKeys);return info;}
 }
 

标签:

素材巴巴 Copyright © 2013-2021 http://www.sucaibaba.com/. Some Rights Reserved. 备案号:备案中。