当前位置: 首页 > news >正文

shiro与jwt前后端分离项目集成

前置准备

导入依赖

shiro依赖

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.3</version>
        </dependency>

jwt依赖

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.13.0</version>
        </dependency>

lombok依赖

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
            <scope>provided</scope>
        </dependency>

redis依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

配置redis

本项目中将token临时存储到redis中,需要在application.yml文件中配置redis中,并添加redisconfig配置类

server:
  port: 8080
spring:
  redis:
    #Redis服务器连接端口
    port: 6379
    #Redis服务器地址
    host: 
    #Redis数据库索引(默认为0)
    database: 0
    #密码
    password: 
    #连接超时时间(毫秒)
    connect-timeout: 1800000
    lettuce:
      pool:
        #连接池最大连接数(使用负值表示没有限制)
        max-active: 20
        #最大阻塞等待时间(负数表示没限制)
        max-wait: -1
        #连接池中的最大空闲连接
        max-idle: 5
        #连接池中的最小空闲连接
        min-idle: 0
package com.zq.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
//key序列化方式
        template.setKeySerializer(redisSerializer);
//value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(600))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
}

认证流程图

请添加图片描述

集成流程

编写token生成及校验工具类

package com.zq.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;

import java.util.Date;

public class TokenUtil {
	//密钥改成自己的
    private static final String SIGN="123456";
    public static String createToken(Integer userId,String username,long expireTime){
        long currentTimeMillis = System.currentTimeMillis();
        return JWT.create().
                withSubject("user")
                .withAudience(String.valueOf(userId))//将user id保存到token里面,作为载荷
                .withClaim("account",username)
                .withClaim("createTime",new Date())
                .withIssuedAt(new Date(currentTimeMillis))
                .withExpiresAt(new Date(currentTimeMillis+ expireTime))
                .sign(Algorithm.HMAC256(SIGN));//以SIGN作为token的秘钥
    }

    public static StatusEnum verifyToken(String token){
        try {
            JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
            return StatusEnum.SUCCESS;
        }catch (TokenExpiredException e){//token过期
            return StatusEnum.EXPIRED;
        }catch (Exception e){//其他异常,校验失败
            return StatusEnum.FAILED;
        }
    }

    public static Integer getUserIdByToken(String token){
        return Integer.valueOf(JWT.decode(token).getAudience().get(0));
    }

    public static String getAccountByToken(String token){
        return JWT.decode(token).getClaim("account").asString();
    }
}


编写token状态枚举类

package com.zq.utils;

public enum StatusEnum{
    SUCCESS,
    EXPIRED,
    FAILED;
}

编写接口统一返回类

package com.zq.vo;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
public class ResultVo<T>{
    private Integer code;
    private String msg;
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private T data;
    public static <T> ResultVo<T> success(String msg){
        ResultVo<T> resultVo= new ResultVo<>();
        resultVo.setCode(ResultEnum.SUCCESS.getCode())
                .setMsg(msg);
        return resultVo;
    }
    public static <T> ResultVo<T> success(String msg,T data){
        ResultVo<T> resultVo= new ResultVo<>();
        resultVo.setCode(ResultEnum.SUCCESS.getCode())
                .setMsg(msg)
                .setData(data);
        return resultVo;
    }
    public static <T> ResultVo<T> fail(String msg){
        ResultVo<T> resultVo= new ResultVo<>();
        resultVo.setCode(ResultEnum.FAIL.getCode())
                .setMsg(msg);
        return resultVo;
    }

    public static <T> ResultVo<T> error(String msg) {
        ResultVo<T> resultVo = new ResultVo<>();
        resultVo.setCode(ResultEnum.ERROR.getCode())
                .setMsg(msg);
        return resultVo;
    }

    public static <T> ResultVo<T> invalid(String msg) {
        ResultVo<T> resultVo = new ResultVo<>();
        resultVo.setCode(ResultEnum.INVALID.getCode())
                .setMsg(msg);
        return resultVo;
    }
}

enum ResultEnum {
    SUCCESS(200),
    INVALID(300),
    FAIL(400),
    ERROR(500);
    private Integer code;

    ResultEnum(Integer code) {
        this.code=code;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }
}

自定义Realm

package com.zq.realms;

import com.zq.pojo.JwtToken;
import com.zq.service.impl.UserService;
import com.zq.utils.StatusEnum;
import com.zq.utils.TokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

//自定义realm
@Slf4j
@Component
public class CustomerRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;
    //授权访问
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.info("doGetAuthorizationInfo");
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        //获得当前subject
        Subject subject= SecurityUtils.getSubject();
        //获得当前的principal,也就是认证完后我们放入的信息

        String username=(String) subject.getPrincipal();
        //添加权限
        info.addStringPermissions(userService.getPermissions(username));
        //添加角色
        info.addRole(userService.getRoles(username));

        return info;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("doGetAuthenticationInfo");
        String token=(String)authenticationToken.getCredentials();
        if(token==null){
            throw new AuthenticationException("token无效");
        }
        StatusEnum statusEnum = TokenUtil.verifyToken(token);
        if(statusEnum.equals(StatusEnum.EXPIRED)){
            throw new AuthenticationException("token过期");
        }
        if(statusEnum.equals(StatusEnum.FAILED)){
            throw new AuthenticationException("token无效");
        }
        String username= TokenUtil.getAccountByToken(token);
        log.info("登录用户为:{}",username);

        //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,不配置的话则使用默认的SimpleCredentialsMatcher
        //用户名,凭证,realm name
        return new SimpleAuthenticationInfo(username,token,this.getName());
    }

    /*
     * 多重写一个support
     * 标识这个Realm是专门用来验证JwtToken,不负责验证其他的token(UsernamePasswordToken)
     * 必须重写此方法,不然Shiro会报错
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

}

自定义UsernamePasswordToken

package com.zq.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.shiro.authc.AuthenticationToken;

@Data
@AllArgsConstructor
public class JwtToken implements AuthenticationToken {
    private String token;
    @Override
    public Object getPrincipal() {
        return token;
    }

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

自定义密码(token)匹配器

package com.zq.config;

import com.zq.utils.TokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;


@Slf4j
@Component
public class CustomHashedCredentialsMatcher extends HashedCredentialsMatcher {

    @Autowired
    private RedisTemplate redisTemplate;

    //自定义你的Token校验逻辑,比如与存储在Redis中的Token做对比
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        log.info("doCredentialsMatch");
        String tokenStr = (String) token.getCredentials();
        Integer id = TokenUtil.getUserIdByToken(tokenStr);
        String redisToken = (String)redisTemplate.opsForValue().get("user:token:"+id);
        if(!tokenStr.equals(redisToken))
            throw new AuthenticationException("非法token");
        return true;
    }
}

编写Shiro配置类

package com.zq.config;

import com.zq.realms.CustomerRealm;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 用来整合shiro框架相关的配置类
 */
@Configuration
public class ShiroConfig {
    @Autowired
    private CustomerRealm customerRealm;
    //创建shiroFilter,负责拦截所有请求
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();

        //给filter设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        //自定义过滤器,JwtFilter,使用LinkedHashMap保证Filter的有序性
        Map<String, Filter> filterMap=new LinkedHashMap<>();
        filterMap.put("jwt",new JwtFilter());
        shiroFilterFactoryBean.setFilters(filterMap);


        //配置系统公共资源
        Map<String,String> map=new LinkedHashMap<>();
        map.put("/test/hello","anon");
        map.put("/test/login","anon");

        //配置需要经过jwt过滤器校验的资源
        map.put("/**","jwt");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    //创建安全管理器,管理Realm数据源
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("myRealm") Realm realm){
        DefaultWebSecurityManager defaultWebSecurityManager=new DefaultWebSecurityManager();

        //给安全管理器设置
        defaultWebSecurityManager.setRealm(realm);
        //关闭ShiroSession,实现Shiro无状态
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        defaultWebSecurityManager.setSubjectDAO(subjectDAO);

        return defaultWebSecurityManager;
    }

    //创建自定义Realm
    @Bean("myRealm")
    public Realm getRealm(CustomHashedCredentialsMatcher customHashedCredentialsMatcher){
        //配置自定义密码匹配器
        customerRealm.setCredentialsMatcher(customHashedCredentialsMatcher);
        return customerRealm;
    }

    //开启Shiro注解支持

    /**
     * 如果userPrefix和proxyTargetClass都为false会导致 aop和shiro权限注解不兼容 资源报错404
     * 因此两个属性至少需要其中一个属性为true才可以
     * 这个Bean的作用是使得@RequiresRoles和@RequiresPermissions注解生效
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator =
                new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    /**
     * 开启aop注解支持
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); // 这里需要注入 SecurityManger 安全管理器
        return authorizationAttributeSourceAdvisor;
    }
}

编写JWT过滤器

package com.zq.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zq.pojo.JwtToken;
import com.zq.vo.ResultVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {

    /**
     * 过滤器拦截请求的入口方法
     * 是否允许访问,如果带有 token,则对 token 进行检查,否则直接拒绝
     * 如果返回true,就流转到下一个链式调用
     * 如果返回false,就会流转到onAccessDenied方法
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        log.info("isAccessAllowed");
        if (isLoginAttempt(request, response)) {//请求头包含Token
            try { //如果存在,则进入 executeLogin 方法,检查 token 是否正确
                executeLogin(request, response);
                return true;
            }catch (AuthenticationException e){
                handlerLoginException(response,e);
            } catch (Exception e) {
                handlerLoginException(response,new AuthenticationException("error"));
            }
        }else {
            handlerLoginException(response,new AuthenticationException("token无效"));
        }
        //如果请求头不存在 Token,直接返回 false
        return false;
    }

    //拒绝访问的请求
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        return false;
    }

    /**
     * 判断用户是否已经登录
     * 检测 header 里面是否包含 Token 字段
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        log.info("isLoginAttempt");
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader("Authorization");
        return token != null;
    }

    /**
     * Shiro认证操作
     * executeLogin实际上就是先调用createToken来获取token,这里我们重写了这个方法,就不会自动去调用createToken来获取token
     * 然后调用getSubject方法来获取当前用户再调用login方法来实现登录
     * 这也解释了我们为什么要自定义jwtToken,因为我们不再使用Shiro默认的UsernamePasswordToken了。
     * */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception{
        log.info("executeLogin");
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader("Authorization");
        JwtToken jwt = new JwtToken(token);
        getSubject(request, response).login(jwt);
        return true;
    }

    /**
     * 在JwtFilter处理逻辑之前,进行跨域处理
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        log.info("preHandle");
        HttpServletRequest req= (HttpServletRequest) request;
        HttpServletResponse res= (HttpServletResponse) response;
        res.setHeader("Access-control-Allow-Origin",req.getHeader("Origin"));
        res.setHeader("Access-control-Allow-Methods","GET,POST,OPTIONS,PUT,DELETE");
        res.setHeader("Access-control-Allow-Headers",req.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
            res.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }
    
    /**
     * 在token校验无效之后返回json信息,这里使用jackson配合原生servlet写法,直接抛出的异常会被jwt过滤器捕获并自己处理,使用全局异常处理无效
     * @param response
     * @param e
     */
    private void handlerLoginException(ServletResponse response,AuthenticationException e){
        response.setContentType("application/json;charset=utf-8");
        try(PrintWriter writer = response.getWriter()){
            ResultVo<Object> resultVo=ResultVo.invalid(e.getMessage());
            ObjectMapper objectMapper=new ObjectMapper();
            writer.print(objectMapper.writeValueAsString(resultVo));
        }catch (IOException ignored){
        }
    }
}

全局异常处理

package com.zq.exception;

import com.zq.vo.ResultVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;


@Slf4j
@RestControllerAdvice
public class GlobalException {

    @ExceptionHandler(value = {AuthorizationException.class})
    public ResultVo<Object> handleAuthorizationException(Exception e){
        return ResultVo.fail("权限不足");
    }

}

编写service

这里代码没从数据库查,写死的测试数据,自己改动一下就行

package com.zq.service.impl;

import com.zq.po.UserPO;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class UserService {
    public UserPO queryUserByName(String username){
        if ("admin".equals(username))
            return new UserPO(1,username);
        return null;
    }
    public String getRoles(String username){
        return "user";
    }
    public List<String> getPermissions(String username){
        List<String> perms = new ArrayList<>();
        perms.add("user:add");
        perms.add("user:upate");
        return perms;
    }
}

对应的User实体类

package com.zq.po;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;


@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserPO {
    private Integer id;
    private String username;
}

编写Controller测试类

package com.zq.controller;

import com.zq.po.UserPO;
import com.zq.service.impl.UserService;
import com.zq.utils.TokenUtil;

import com.zq.vo.ResultVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@RestController
@RequestMapping("test")
public class TestController {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private UserService userService;

    @GetMapping("hello")
    public ResultVo<Object> hello(){
        return ResultVo.success("Hello");
    }

    @GetMapping("world")
    public ResultVo<Object> world(){
        return ResultVo.success("world");
    }

    @PostMapping("login")
    public ResultVo<Object> login(String username,String password){
        //TODO
        UserPO userPO = userService.queryUserByName(username);
        if(userPO==null)
            return ResultVo.fail("用户不存在");
        String token = TokenUtil.createToken(userPO.getId(), username, 2L * 60 * 60 * 1000);
        redisTemplate.opsForValue().set("user:token:"+userPO.getId(),token,2*60, TimeUnit.MINUTES);
        Map<String,String> map=new HashMap<>();
        map.put("token",token);
        return ResultVo.success("登录成功",map);
    }
    @GetMapping("user")
    @RequiresRoles("user")
    public ResultVo<Object> user(){
        return ResultVo.success("访问uer成功");
    }

    @GetMapping("add")
    @RequiresPermissions("user:add")
    public ResultVo<Object> add(){
        return ResultVo.success("访问add成功");
    }

    @GetMapping("delete")
    @RequiresPermissions("user:delete")
    public ResultVo<Object> delete(){
        return ResultVo.success("访问delete成功");
    }

    @GetMapping("insert")
    @RequiresRoles("admin")
    public ResultVo<Object> insert(){
        System.out.println(SecurityUtils.getSubject().hasRole("admin"));
        return ResultVo.success("访问insert成功");
    }

    @GetMapping("in")
    @RequiresAuthentication
    public ResultVo<Object> in(){
        return ResultVo.success("访问in成功");
    }
}

测试

调用公共接口

公共接口是不经过JWT过滤器校验的接口

hello接口

在这里插入图片描述
控制台无输出

login接口

在这里插入图片描述
在这里插入图片描述

访问受限接口

使用有效token

在这里插入图片描述

在这里插入图片描述

使用无效token

在这里插入图片描述

不携带token

在这里插入图片描述

携带token访问需要角色的接口

角色不满足

这里是写死的只有user角色
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

角色满足

在这里插入图片描述
在这里插入图片描述

使用token访问权限接口

权限不满足

在这里插入图片描述
在这里插入图片描述

权限满足

在这里插入图片描述
在这里插入图片描述

控制台打印情况

这里给出部分在调试阶段控制台的打印情况

[19:36:05.741] INFO  com.zq.config.JwtFilter 90 preHandle - preHandle
[19:36:05.741] INFO  com.zq.config.JwtFilter 30 isAccessAllowed - isAccessAllowed
[19:36:05.742] INFO  com.zq.config.JwtFilter 59 isLoginAttempt - isLoginAttempt
[19:36:05.742] INFO  com.zq.config.JwtFilter 73 executeLogin - executeLogin
[19:36:05.742] INFO  com.zq.realms.CustomerRealm 48 doGetAuthenticationInfo - doGetAuthenticationInfo
[19:36:05.761] INFO  com.zq.realms.CustomerRealm 61 doGetAuthenticationInfo - 登录用户为:admin
[19:36:05.762] INFO  com.zq.config.CustomHashedCredentialsMatcher 24 doCredentialsMatch - doCredentialsMatch

[19:36:24.170] INFO  com.zq.config.JwtFilter 90 preHandle - preHandle
[19:36:24.170] INFO  com.zq.config.JwtFilter 30 isAccessAllowed - isAccessAllowed
[19:36:24.171] INFO  com.zq.config.JwtFilter 59 isLoginAttempt - isLoginAttempt
[19:36:24.171] INFO  com.zq.config.JwtFilter 73 executeLogin - executeLogin
[19:36:24.171] INFO  com.zq.realms.CustomerRealm 48 doGetAuthenticationInfo - doGetAuthenticationInfo
[19:36:24.173] INFO  com.zq.realms.CustomerRealm 61 doGetAuthenticationInfo - 登录用户为:admin
[19:36:24.173] INFO  com.zq.config.CustomHashedCredentialsMatcher 24 doCredentialsMatch - doCredentialsMatch

[19:37:40.395] INFO  com.zq.config.JwtFilter 90 preHandle - preHandle
[19:37:40.396] INFO  com.zq.config.JwtFilter 30 isAccessAllowed - isAccessAllowed
[19:37:40.396] INFO  com.zq.config.JwtFilter 59 isLoginAttempt - isLoginAttempt
[19:37:40.396] INFO  com.zq.config.JwtFilter 73 executeLogin - executeLogin
[19:37:40.396] INFO  com.zq.realms.CustomerRealm 48 doGetAuthenticationInfo - doGetAuthenticationInfo

[19:38:14.030] INFO  com.zq.config.JwtFilter 90 preHandle - preHandle
[19:38:14.031] INFO  com.zq.config.JwtFilter 30 isAccessAllowed - isAccessAllowed
[19:38:14.031] INFO  com.zq.config.JwtFilter 59 isLoginAttempt - isLoginAttempt

[19:40:35.457] INFO  com.zq.config.JwtFilter 90 preHandle - preHandle
[19:40:35.458] INFO  com.zq.config.JwtFilter 30 isAccessAllowed - isAccessAllowed
[19:40:35.458] INFO  com.zq.config.JwtFilter 59 isLoginAttempt - isLoginAttempt

[19:40:47.404] INFO  com.zq.config.JwtFilter 90 preHandle - preHandle
[19:40:47.404] INFO  com.zq.config.JwtFilter 30 isAccessAllowed - isAccessAllowed
[19:40:47.404] INFO  com.zq.config.JwtFilter 59 isLoginAttempt - isLoginAttempt

[19:40:51.141] INFO  com.zq.config.JwtFilter 90 preHandle - preHandle
[19:40:51.141] INFO  com.zq.config.JwtFilter 30 isAccessAllowed - isAccessAllowed
[19:40:51.141] INFO  com.zq.config.JwtFilter 59 isLoginAttempt - isLoginAttempt
[19:40:51.141] INFO  com.zq.config.JwtFilter 73 executeLogin - executeLogin
[19:40:51.142] INFO  com.zq.realms.CustomerRealm 48 doGetAuthenticationInfo - doGetAuthenticationInfo
[19:40:51.143] INFO  com.zq.realms.CustomerRealm 61 doGetAuthenticationInfo - 登录用户为:admin
[19:40:51.143] INFO  com.zq.config.CustomHashedCredentialsMatcher 24 doCredentialsMatch - doCredentialsMatch
[19:40:51.273] INFO  com.zq.realms.CustomerRealm 30 doGetAuthorizationInfo - doGetAuthorizationInfo

[19:44:13.431] INFO  com.zq.config.JwtFilter 90 preHandle - preHandle
[19:44:13.431] INFO  com.zq.config.JwtFilter 30 isAccessAllowed - isAccessAllowed
[19:44:13.432] INFO  com.zq.config.JwtFilter 59 isLoginAttempt - isLoginAttempt
[19:44:13.432] INFO  com.zq.config.JwtFilter 73 executeLogin - executeLogin
[19:44:13.432] INFO  com.zq.realms.CustomerRealm 48 doGetAuthenticationInfo - doGetAuthenticationInfo
[19:44:13.433] INFO  com.zq.realms.CustomerRealm 61 doGetAuthenticationInfo - 登录用户为:admin
[19:44:13.433] INFO  com.zq.config.CustomHashedCredentialsMatcher 24 doCredentialsMatch - doCredentialsMatch
[19:44:13.474] INFO  com.zq.realms.CustomerRealm 30 doGetAuthorizationInfo - doGetAuthorizationInfo

[19:45:12.566] INFO  com.zq.config.JwtFilter 90 preHandle - preHandle
[19:45:12.566] INFO  com.zq.config.JwtFilter 30 isAccessAllowed - isAccessAllowed
[19:45:12.566] INFO  com.zq.config.JwtFilter 59 isLoginAttempt - isLoginAttempt
[19:45:12.566] INFO  com.zq.config.JwtFilter 73 executeLogin - executeLogin
[19:45:12.566] INFO  com.zq.realms.CustomerRealm 48 doGetAuthenticationInfo - doGetAuthenticationInfo
[19:45:12.567] INFO  com.zq.realms.CustomerRealm 61 doGetAuthenticationInfo - 登录用户为:admin
[19:45:12.568] INFO  com.zq.config.CustomHashedCredentialsMatcher 24 doCredentialsMatch - doCredentialsMatch
[19:45:12.616] INFO  com.zq.realms.CustomerRealm 30 doGetAuthorizationInfo - doGetAuthorizationInfo

[19:45:59.999] INFO  com.zq.config.JwtFilter 90 preHandle - preHandle
[19:45:59.999] INFO  com.zq.config.JwtFilter 30 isAccessAllowed - isAccessAllowed
[19:45:59.999] INFO  com.zq.config.JwtFilter 59 isLoginAttempt - isLoginAttempt
[19:45:59.999] INFO  com.zq.config.JwtFilter 73 executeLogin - executeLogin
[19:45:59.999] INFO  com.zq.realms.CustomerRealm 48 doGetAuthenticationInfo - doGetAuthenticationInfo
[19:46:00.000] INFO  com.zq.realms.CustomerRealm 61 doGetAuthenticationInfo - 登录用户为:admin
[19:46:00.001] INFO  com.zq.config.CustomHashedCredentialsMatcher 24 doCredentialsMatch - doCredentialsMatch
[19:46:00.047] INFO  com.zq.realms.CustomerRealm 30 doGetAuthorizationInfo - doGetAuthorizationInfo

源码

https://gitee.com/codewarning/shiro_demo

相关文章:

  • web3 solidity 基础 ERC20 大白话搞懂
  • 《C++类中的基本常识》
  • JavaEE——File类
  • CANoe-如何模拟CAN总线网关通信(满满都是细节)
  • 请求转发与重定向
  • 如何使用OpenCV的随机森林(Python)
  • matlab神经网络预测数据,Matlab神经网络预测复数
  • 毕业设计 基于单片机的智能蓝牙密码锁设计与实现
  • 【线上实习项目】助力你的校招!
  • 吊打面试官系列之--吃透Spring ioc 和 aop (中)
  • Matlab制作GUI
  • Spring Data JPA或Spring Data JDBC中Like和Containing区别
  • SpringMVC04之JSON和全局异常处理
  • <C++> list容器本质|常用接口|自定义排序规则
  • 【Matlab】简单控制系统建模(控制系统工具箱)
  • Angular 响应式表单 基础例子
  • ES6系列(二)变量的解构赋值
  • ES6系统学习----从Apollo Client看解构赋值
  • JAVA 学习IO流
  • mockjs让前端开发独立于后端
  • PHP面试之三:MySQL数据库
  • react-native 安卓真机环境搭建
  • thinkphp5.1 easywechat4 微信第三方开放平台
  • Vue 2.3、2.4 知识点小结
  • Vultr 教程目录
  • 不用申请服务号就可以开发微信支付/支付宝/QQ钱包支付!附:直接可用的代码+demo...
  • 技术攻略】php设计模式(一):简介及创建型模式
  • 聊聊hikari连接池的leakDetectionThreshold
  • 目录与文件属性:编写ls
  • 设计模式(12)迭代器模式(讲解+应用)
  • 详解NodeJs流之一
  • ​LeetCode解法汇总2670. 找出不同元素数目差数组
  • #LLM入门|Prompt#2.3_对查询任务进行分类|意图分析_Classification
  • (4)(4.6) Triducer
  • (poj1.3.2)1791(构造法模拟)
  • (react踩过的坑)Antd Select(设置了labelInValue)在FormItem中initialValue的问题
  • (ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY)讲解
  • (笔试题)合法字符串
  • (附源码)springboot 基于HTML5的个人网页的网站设计与实现 毕业设计 031623
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (六)激光线扫描-三维重建
  • (十八)devops持续集成开发——使用docker安装部署jenkins流水线服务
  • (四)linux文件内容查看
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
  • (原创)boost.property_tree解析xml的帮助类以及中文解析问题的解决
  • (转)Android学习系列(31)--App自动化之使用Ant编译项目多渠道打包
  • ... 是什么 ?... 有什么用处?
  • .net FrameWork简介,数组,枚举
  • .NET 使用 JustAssembly 比较两个不同版本程序集的 API 变化
  • .NET/C# 异常处理:写一个空的 try 块代码,而把重要代码写到 finally 中(Constrained Execution Regions)
  • .NET开源全面方便的第三方登录组件集合 - MrHuo.OAuth
  • .NET文档生成工具ADB使用图文教程
  • .Net语言中的StringBuilder:入门到精通
  • @property括号内属性讲解
  • [ 环境搭建篇 ] 安装 java 环境并配置环境变量(附 JDK1.8 安装包)