前置准备
导入依赖
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