shiro+springboot+jwt+redis

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存储方式 <userName,token>
   虽然违背了 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
   

结构
在这里插入图片描述

pom


<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!-- 阿里连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!-- mysql依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- mybatis-plus 依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>
        <!-- 代码生成器依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.1</version>
        </dependency>
        <!-- 代码生成器依赖 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.2</version>
        </dependency>
        <!-- shiro+jwt -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>
        <!-- lombok 依赖 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- jwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
        <!-- swagger2 依赖 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!-- Swagger第三方UI依赖 -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.6</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>
        </dependency>
        <!-- 日期处理工具 -->
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
        </dependency>
        <!-- common -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>
        <!-- common -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.28</version>
        </dependency>

    </dependencies>
yml
server:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql://localhost:3307/shiro?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
    username: root
    password: phpts
    driver-class-name: com.mysql.jdbc.Driver
  jackson:
    time-zone: Asia/Shanghai
  redis:
    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 {
    /**
     * 分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        //针对 update和 delete语句,作用: 阻止恶意的全表更新删除
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());

        return interceptor;
    }
}

@Component
public class MyBatisPlusFillTime implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        //插入时间
        this.setFieldValByName("createTime", new Date(), metaObject);
        this.setFieldValByName("updateTime", new Date(), metaObject);

    }

    @Override
    public void updateFill(MetaObject metaObject) {
        //更新时间
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
}
---------------------------------
包configs -->RedisConfig
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    @Bean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        //参照StringRedisTemplate内部实现指定序列化器
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(keySerializer());
        redisTemplate.setHashKeySerializer(keySerializer());
        redisTemplate.setValueSerializer(valueSerializer());
        redisTemplate.setHashValueSerializer(valueSerializer());
        return redisTemplate;
    }

    private RedisSerializer<String> keySerializer(){
        return new StringRedisSerializer();
    }

    //使用Jackson序列化器
    private RedisSerializer<Object> 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自带session
        DefaultSubjectDAO 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<String, Filter> filters = new HashMap<>();
        filters.put("jwt", new JwtFilter());
        factoryBean.setFilters(filters);

        Map<String, String> 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,不保存用户登录状态
    @Bean
    protected SessionStorageEvaluator sessionStorageEvaluator(){
        DefaultWebSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
        sessionStorageEvaluator.setSessionStorageEnabled(false);
        return sessionStorageEvaluator;
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        autoProxyCreator.setProxyTargetClass(true);
        return autoProxyCreator;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){return new LifecycleBeanPostProcessor();}

    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     *
     * @param
     * @return
     */
    @Bean
    public 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 {

    @Bean
    public 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<SecurityContext> securityContexts() {
        List<SecurityContext> 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<SecurityReference> defaultAuthPath() {
        List<SecurityReference> 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<ApiKey> securitySchemes() {
        List<ApiKey> 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 {
    @Bean
    public CorsFilter corsFilter(){
        CorsConfiguration config =new CorsConfiguration();
        //允许跨越发送cookie
        config.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 {
    @Getter
    private int code;
    @Getter
    private 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("\n\n SqlException===========  Exception Start  ===== \n"+e+"\n "+"SqlException===========  Exception End  =====\n\n");
        }else {
            log.info("\n\n RuntimeException===========  Exception Start  ===== \n"+e+"\n "+"RuntimeException===========  Exception End  =====\n\n");
        }
        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;
    @Getter
    private int code ;

    @Getter
    private 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;

    @Override
    public 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> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }

    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }
}

---------------------------------
包utils -->jwt

/**
 * Created with IntelliJ IDEA
 * Time: 17:49
 *
 * 所有请求都要先走filter 进行校验是否携带token
 * 每次请求都走一遍
 */
public class JwtFilter extends BasicHttpAuthenticationFilter {

    /**
     * 校验是否携带token
     * 携带:true
     */
    @Override
    protected 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
     */
    @Override
    protected 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生命周期,容器未注入redis
        RedisUtil redisUtil = SpringContextUtil.getBean(RedisUtil.class);
        TokenLife tokenLife = SpringContextUtil.getBean(TokenLife.class);
        //redis 续期
        redisUtil.expire(JwtTokenUtil.getUsername(authorization),tokenLife.getLife());
        return true;
    }

    /**
     * 认证或授权异常捕捉返回给前端
     */
    @SneakyThrows
    @Override
    protected 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;
    }

    /**
     * 对跨域提供支持
     */
    @Override
    protected 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 {
    @Autowired
    private UserInfoService userInfoService;
    @Autowired
    private RoleAuthService roleAuthService;
    @Autowired
    private AuthInfoService authInfoService;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private TokenLife tokenLife;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 授权
     * @param principalCollection
     * @return
     */
    @Override
    protected 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<String> roles = new HashSet<>();
        roles.add(String.valueOf(roleId));
        //3 根据角色信息获取权限信息
        Set<String> authodKeys = new HashSet<>();
        // 3.1 去角色权限信息关联表去查所有的角色权限
        List<RoleAuth> allAuthorList = roleAuthService.list(new QueryWrapper<RoleAuth>().eq("role_id", roleId));
        if(CollectionUtils.isNotEmpty(allAuthorList)){
            // 3.2 去重的权限id
            List<Integer> distinctAuthorId = allAuthorList.stream().map(e -> e.getAuthId()).collect(Collectors.toList());
            // 3.3 所有的权限信息
            List<AuthInfo> authInfoList = authInfoService.list(new QueryWrapper<AuthInfo>().in("id",distinctAuthorId ));
            if(CollectionUtils.isNotEmpty(authInfoList)){
                // 3.4 提取权限key: auth_key
                authodKeys = 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
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("==================token认证=============");
        //获取前端界面传来的token
        String token = (String) authenticationToken.getPrincipal();
        if(StringUtils.isBlank(token)){
            throw new MyCustomException(401,"token不存在!");
        }
        //1 根据token 获取 username
        String 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;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }

}





/**
 * Created with IntelliJ IDEA
 * Time: 10:39
 */
@Component
public class JwtTokenUtil {
    //此处jwt 配合redis 使用,jwt只是生成token 和校验 token是否正确,不进行超时校验,超时放到redis中:redis<username,token>
    //且每次请求都刷新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<String, Object> map = new HashMap<String, Object>();
            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 {


    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public RedisUtil(RedisTemplate<String, Object> 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<Object,Object> hmget(String key){
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String,Object> 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<String,Object> 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<Object> 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<Object> 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<Object> 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<Object> 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<Object> rangeList(String listKey, long start, long end) {
        //绑定操作
        BoundListOperations<String, Object> boundValueOperations = redisTemplate.boundListOps(listKey);
        //查询数据
        return boundValueOperations.range(start, end);
    }
    /**
     * 弹出右边的值 --- 并且移除这个值
     * @param listKey
     */
    public Object rifhtPop(String listKey){
        //绑定操作
        BoundListOperations<String, Object> 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");


    @Getter
    private int code;

    @Getter
    private String msg;

    HttpStatusCode(int code, String msg){
        this.code = code;
        this.msg = msg;
    }
}





@Data
public class R<T> {

    // 结果状态码
    private int code;

    // 响应信息
    private String msg;

    // 总条数
    private Long total;

    // 响应数据
    private T data;

    // 接口请求时间
    private long timestamp ;

    public R (){
        this.timestamp = System.currentTimeMillis();
    }

    public static <T> R<T> success(T data){
        R R1 = new R();
        R1.setCode(HttpStatusCode.OK.getCode());
        R1.setMsg(HttpStatusCode.OK.getMsg());
        R1.setData(data);
        return  R1;
    }

    public static <T> R<T> fail(int code, String msg){
        R R1 = new R();
        R1.setCode(code);
        R1.setMsg(msg);
        return R1;
    }

    public static <T> R<T> 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 {
    @Resource
    private 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);
    }
}


== service

public 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";
    @Autowired
    private UserInfoService userService;
    @Autowired
    private RedisUtil redisUtil;
    /**
     * 角色
     */
    @Autowired
    private RoleAuthService roleAuthService;
    /**
     * 权限
     */
    @Autowired
    private AuthInfoService authInfoService;
    /**
     * token 生命周期
     */
    @Resource
    private TokenLife tokenLife;

    @Override
    public R login(UserInfo userInfo) {
        UserInfo user = userService.getOne(new QueryWrapper<UserInfo>()
                .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中的token
        redisUtil.del(user.getUsername());
        //保存token 到redis,10min 超时
        redisUtil.set(user.getUsername(),token,tokenLife.getLife());
        user.setPassword(null);
        //获取登录用户角色权限信息,前端有可能需要
        Map<String, Object> 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"));
        }});
    }

    @Override
    public R loginMany(UserInfo userInfo) {
        UserInfo user = userService.getOne(new QueryWrapper<UserInfo>()
                .eq("username", userInfo.getUsername()));
        //用户不存在
        if (user == null) {
            return R.fail("用户名或密码不正确!");
        }
        //密码 md5+盐,经过两次hash
        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 = 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<String, Object> 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"));
                          }}
        );
    }

    @Override
    public Object logout(Long userid) {
        return null;
    }


    /**
     * 登录获取权限角色一并返给前端
     */
    public  Map<String,Object>  getRoleAndPermission(UserInfo user) {
        //1 存放角色权限
        Map<String,Object> info = new HashMap<>();
        //2 根据用户获取角色信息
        Integer roleId = user.getRoleId();
        Set<String> roles = new HashSet<>();
        roles.add(String.valueOf(roleId));
        //3 根据角色信息获取权限信息
        Set<String> authodKeys = new HashSet<>();
        // 3.1 去角色权限信息关联表去查所有的角色权限
        List<RoleAuth> allAuthorList = roleAuthService.list(new QueryWrapper<RoleAuth>().eq("role_id", roleId));
        if(CollectionUtils.isNotEmpty(allAuthorList)){
            // 3.2 去重的权限id
            List<Integer> distinctAuthorId = allAuthorList.stream().map(e -> e.getAuthId()).collect(Collectors.toList());
            // 3.3 所有的权限信息
            List<AuthInfo> authInfoList = authInfoService.list(new QueryWrapper<AuthInfo>().in("id",distinctAuthorId ));
            if(CollectionUtils.isNotEmpty(authInfoList)){
                // 3.4 提取权限key: auth_key
                authodKeys = authInfoList.stream().map(e -> e.getAuthKey()).collect(Collectors.toSet());
            }
        }
        //4 角色列表
        info.put("role",roles);
        //5 权限列表
        info.put("permission",authodKeys);
        return info;
    }
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring Boot是一个开源的Java框架,用于构建独立的、可执行的、生产级的Spring应用程序。它极大地简化了Spring应用程序的搭建和部署过程,提供了一整套开箱即用的特性和插件,极大地提高了开发效率。 Shiro是一个强大且灵活的开源Java安全框架,提供了身份验证、授权、加密和会话管理等功能,用于保护应用程序的安全。它采用插件化的设计,支持与Spring等常用框架的无缝集成,使开发者能够轻松地在应用程序中添加安全功能。 JWT(JSON Web Token)是一种用于在客户端和服务端之间传输安全信息的开放标准。它使用JSON格式对信息进行包装,并使用数字签名进行验证,确保信息的完整性和安全性。JWT具有无状态性、可扩展性和灵活性的特点,适用于多种应用场景,例如身份验证和授权。 Redis是一个开源的、高性能的、支持多种数据结构的内存数据库,同时也可以持久化到磁盘中。它主要用于缓存、消息队列、会话管理等场景,为应用程序提供高速、可靠的数据访问服务。Redis支持丰富的数据类型,并提供了强大的操作命令,使开发者能够灵活地处理各种数据需求。 综上所述,Spring Boot结合ShiroJWTRedis可以构建一个安全、高性能的Java应用程序。Shiro提供了强大的安全功能,包括身份验证和授权,保护应用程序的安全;JWT用于安全传输信息,确保信息的完整性和安全性;Redis作为缓存和持久化数据库,提供了高速、可靠的数据访问服务。通过使用这些技术,开发者能够快速、高效地构建出符合安全和性能需求的应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值