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

1 篇文章 0 订阅

前置准备

导入依赖

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值