-
登录时,密码验证通过,取当前时间戳生成签名Token,放在Response Header的Authorization属性中,同时在缓存中记录值为当前时间戳的RefreshToken,并设置有效期。
-
客户端请求每次携带Token进行请求。
-
服务端每次校验请求的Token有效后,同时比对Token中的时间戳与缓存中的RefreshToken时间戳是否一致,一致则判定Token有效。
-
当请求的Token被验证时抛出
TokenExpiredException
异常时说明Token过期,校验时间戳一致后重新生成Token并调用登录方法。
-
每次生成新的Token后,同时要根据新的时间戳更新缓存中的RefreshToken,以保证两者时间戳一致。
public class ShiroConfig {
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public static DefaultAdvisorAutoProxyCreator getLifecycleBeanPostProcessor() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
// 强制使用cglib
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
@Bean
public DefaultWebSecurityManager securityManager(ShiroRealm shiroRealm,ShiroCacheManager shiroCacheManager){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm);
//关闭shiro自带的session
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
//自定义缓存管理
securityManager.setCacheManager(shiroCacheManager);
return securityManager;
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
// 添加jwt过滤器
Map<string, filter=""> filterMap = new HashMap<>();
filterMap.put("jwt", jwtFilter());
filterMap.put("logout", new SystemLogoutFilter());
shiroFilter.setFilters(filterMap);</string,>
//拦截器
Map<string,string> filterRuleMap = new LinkedHashMap<>();
filterRuleMap.put("/logout", "logout");
filterRuleMap.put("/**", "jwt");
shiroFilter.setFilterChainDefinitionMap(filterRuleMap);</string,string>
return shiroFilter;
@Bean
public JwtFilter jwtFilter(){
return new JwtFilter();此处为AccessToken
用户验证以及权限验证的地方,用户验证多加了一个校验,就是我们当前请求的token中包含的时间戳与缓存中的RefreshToken对比,一致才验证通过。
@Service
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private IBpUserService userService;
@Autowired
private IBpRoleService roleService;
@Autowired
private IBpAuthorityService bpAuthorityService;
@Autowired
private CacheClient cacheClient;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
* 用户名信息验证
* @param auth
* @return
* @throws AuthenticationException
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth)
throws AuthenticationException {
String token = (String)auth.getPrincipal();
String account = JwtUtil.getClaim(token,SecurityConsts.ACCOUNT);
if (account == null) {
throw new AuthenticationException("token invalid");
BpUser bpUserInfo = userService.findUserByAccount(account);
if (bpUserInfo == null) {
throw new AuthenticationException("BpUser didn't existed!");
String refreshTokenCacheKey = SecurityConsts.PREFIX_SHIRO_REFRESH_TOKEN + account;
if (JwtUtil.verify(token) && cacheClient.exists(refreshTokenCacheKey)) {
String currentTimeMillisRedis = cacheClient.get(refreshTokenCacheKey);
// 获取AccessToken时间戳,与RefreshToken的时间戳对比
if (JwtUtil.getClaim(token, SecurityConsts.CURRENT_TIME_MILLIS).equals(currentTimeMillisRedis)) {
return new SimpleAuthenticationInfo(token, token, "shiroRealm");
throw new AuthenticationException("Token expired or incorrect.");
* 检查用户权限
* @param principals
* @return
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
String account = JwtUtil.getClaim(principals.toString(), SecurityConsts.ACCOUNT);
BpUser bpUserInfo = userService.findUserByAccount(account);
//获取用户角色
List<bprole> bpRoleList = roleService.findRoleByUserId(bpUserInfo.getId());
//获取权限
List<object> bpAuthorityList = bpAuthorityService.findByUserId(bpUserInfo.getId());
for(BpRole bpRole : bpRoleList){
authorizationInfo.addRole(bpRole.getName());
for(Object auth: bpAuthorityList){
authorizationInfo.addStringPermission(auth.toString());
return authorizationInfo;
这里我们定义了一些常量,其中有请求头包含的Token的属性,以及放入缓存中的Key
public class SecurityConsts {
public static final String LOGIN_SALT = "storyweb-bp";
//request请求头属性
public static final String REQUEST_AUTH_HEADER="Authorization";
//JWT-account
public static final String ACCOUNT = "account";
//Shiro redis 前缀
public static final String PREFIX_SHIRO_CACHE = "storyweb-bp:cache:";
//redis-key-前缀-shiro:refresh_token
public final static String PREFIX_SHIRO_REFRESH_TOKEN = "storyweb-bp:refresh_token:";
//JWT-currentTimeMillis
public final static String CURRENT_TIME_MILLIS = "currentTimeMillis";
# RefreshToken过期时间,单位:分钟, 24*60=1440
refreshTokenExpireTime: 1440
# shiro缓存有效期,单位分钟,2*60=120
shiroCacheExpireTime: 120
# token加密密钥
secretKey: storywebkey
@ConfigurationProperties(prefix = "token")
@Data
public class JwtProperties {
//token过期时间,单位分钟
Integer tokenExpireTime;
//刷新Token过期时间,单位分钟
Integer refreshTokenExpireTime;
//Shiro缓存有效期,单位分钟
Integer shiroCacheExpireTime;
//token加密密钥
String secretKey;
当然了,你需要在SpringBoot的Application启动类中,加入注解:
@EnableConfigurationProperties({JwtProperties.class})
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;
接下来是Jwt的Fiter,集成自Shiro的BasicHttpAuthenticationFilter,这里的注释比较详细。
public class JwtFilter extends BasicHttpAuthenticationFilter {
private Logger LOGGER = LoggerFactory.getLogger(this.getClass());
@Autowired
CacheClient cacheClient;
@Autowired
JwtProperties jwtProperties;
* 检测Header里Authorization字段
* 判断是否登录
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
HttpServletRequest req = (HttpServletRequest) request;
String authorization = req.getHeader(SecurityConsts.REQUEST_AUTH_HEADER);
return authorization != null;
* 登录验证
* @param request
* @param response
* @return
* @throws Exception
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String authorization = httpServletRequest.getHeader(SecurityConsts.REQUEST_AUTH_HEADER);
JwtToken token = new JwtToken(authorization);
// 提交给realm进行登入,如果错误他会抛出异常并被捕获
getSubject(request, response).login(token);
// 绑定上下文
String account = JwtUtil.getClaim(authorization, SecurityConsts.ACCOUNT);
UserContext userContext= new UserContext(new LoginUser(account));
// 如果没有抛出异常则代表登入成功,返回true
return true;
* 刷新AccessToken,进行判断RefreshToken是否过期,未过期就返回新的AccessToken且继续正常访问
private boolean refreshToken(ServletRequest request, ServletResponse response) {
// 获取AccessToken(Shiro中getAuthzHeader方法已经实现)
String token = this.getAuthzHeader(request);
// 获取当前Token的帐号信息
String account = JwtUtil.getClaim(token, SecurityConsts.ACCOUNT);
String refreshTokenCacheKey = SecurityConsts.PREFIX_SHIRO_REFRESH_TOKEN + account;
// 判断Redis中RefreshToken是否存在
if (cacheClient.exists(refreshTokenCacheKey)) {
// 获取RefreshToken时间戳,及AccessToken中的时间戳
// 相比如果一致,进行AccessToken刷新
String currentTimeMillisRedis = cacheClient.get(refreshTokenCacheKey);
String tokenMillis=JwtUtil.getClaim(token, SecurityConsts.CURRENT_TIME_MILLIS);
if (tokenMillis.equals(currentTimeMillisRedis)) {
// 设置RefreshToken中的时间戳为当前最新时间戳
String currentTimeMillis = String.valueOf(System.currentTimeMillis());
Integer refreshTokenExpireTime = jwtProperties.refreshTokenExpireTime;
cacheClient.set(refreshTokenCacheKey, currentTimeMillis,refreshTokenExpireTime*60l);
// 刷新AccessToken,为当前最新时间戳
token = JwtUtil.sign(account, currentTimeMillis);
// 使用AccessToken 再次提交给ShiroRealm进行认证,如果没有抛出异常则登入成功,返回true
JwtToken jwtToken = new JwtToken(token);
this.getSubject(request, response).login(jwtToken);
// 设置响应的Header头新Token
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader(SecurityConsts.REQUEST_AUTH_HEADER, token);
httpServletResponse.setHeader("Access-Control-Expose-Headers", SecurityConsts.REQUEST_AUTH_HEADER);
return true;
return false;
* 是否允许访问
* @param request
* @param response
* @param mappedValue
* @return
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (isLoginAttempt(request, response)) {
try {
this.executeLogin(request, response);
} catch (Exception e) {
String msg = e.getMessage();
Throwable throwable = e.getCause();
if (throwable != null && throwable instanceof SignatureVerificationException) {
msg = "Token或者密钥不正确(" + throwable.getMessage() + ")";
} else if (throwable != null && throwable instanceof TokenExpiredException) {
// AccessToken已过期
if (this.refreshToken(request, response)) {
return true;
} else {
msg = "Token已过期(" + throwable.getMessage() + ")";
} else {
if (throwable != null) {
msg = throwable.getMessage();
this.response401(request, response, msg);
return false;
return true;
* 401非法请求
* @param req
* @param resp
private void response401(ServletRequest req, ServletResponse resp,String msg) {
HttpServletResponse httpServletResponse = (HttpServletResponse) resp;
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
PrintWriter out = null;
try {
out = httpServletResponse.getWriter();
Result result = new Result();
result.setResult(false);
result.setCode(Constants.PASSWORD_CHECK_INVALID);
result.setMessage(msg);
out.append(JSON.toJSONString(result));
} catch (IOException e) {
LOGGER.error("返回Response信息出现IOException异常:" + e.getMessage());
} finally {
if (out != null) {
out.close();
这里再重复一下:当请求验证Token时抛出
TokenExpiredException
异常后,校验缓存中的RefreshToken的时间戳是否与当前请求Token时间戳一致,倘若一致,则重新生成Token,以当前时间戳更新缓存中的RefreshToken时间戳;倘若不一致,则以Json格式直接响应401未登录错误。
采用前后端分离的方式,我们的401就需要直接返回JSON格式的响应。
@Component
public class JwtUtil {
@Autowired
JwtProperties jwtProperties;
@Autowired
private static JwtUtil jwtUtil;
@PostConstruct
public void init() {
jwtUtil = this;
jwtUtil.jwtProperties = this.jwtProperties;
* 校验token是否正确
* @param token
* @return
public static boolean verify(String token) {
String secret = getClaim(token, SecurityConsts.ACCOUNT) + jwtUtil.jwtProperties.secretKey;
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.build();
verifier.verify(token);
return true;
* 获得Token中的信息无需secret解密也能获得
* @param token
* @param claim
* @return
public static String getClaim(String token, String claim) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim(claim).asString();
} catch (JWTDecodeException e) {
return null;
* 生成签名,5min后过期
* @param account
* @param currentTimeMillis
* @return
public static String sign(String account, String currentTimeMillis) {
// 帐号加JWT私钥加密
String secret = account + jwtUtil.jwtProperties.getSecretKey();
// 此处过期时间,单位:毫秒
Date date = new Date(System.currentTimeMillis() + jwtUtil.jwtProperties.getTokenExpireTime()*60*1000l);
Algorithm algorithm = Algorithm.HMAC256(secret);
return JWT.create()
.withClaim(SecurityConsts.ACCOUNT, account)
.withClaim(SecurityConsts.CURRENT_TIME_MILLIS, currentTimeMillis)
.withExpiresAt(date)
.sign(algorithm);
@Data
public class LoginUser implements Serializable {
private static final long serialVersionUID = 1L;
public Long userId; // 主键ID
public String account; // 账号
public String name; // 姓名
public LoginUser() {
public LoginUser(String account) {
this.account=account;
public LoginUser(Long userId, String account, String name) {
this.userId = userId;
this.account = account;
this.name = name;
public class UserContext implements AutoCloseable {
static final ThreadLocal<loginuser> current = new ThreadLocal<>();</loginuser>
public UserContext(LoginUser user) {
current.set(user);
public static LoginUser getCurrentUser() {
return current.get();
public void close() {
current.remove();
public class ShiroCacheManager implements CacheManager {
@Autowired
CacheClient cacheClient;
@Override
public <K, V> Cache<K, V> getCache(String s) throws CacheException {
return new ShiroCache<K,V>(cacheClient);
* 重写Shiro的Cache保存读取
* @param <K>
* @param <V>
public class ShiroCache<K,V> implements Cache<K,V> {
private CacheClient cacheClient;
public ShiroCache(CacheClient cacheClient) {
this.cacheClient = cacheClient;
* 获取缓存
* @param key
* @return
* @throws CacheException
@Override
public Object get(Object key) throws CacheException {
String tempKey= this.getKey(key);
if(cacheClient.exists(tempKey)){
return cacheClient.getObject(tempKey);
return null;
* 保存缓存
* @param key
* @param value
* @return
* @throws CacheException
@Override
public Object put(Object key, Object value) throws CacheException {
return cacheClient.setObject(this.getKey(key), value);
* 移除缓存
* @param key
* @return
* @throws CacheException
@Override
public Object remove(Object key) throws CacheException {
String tempKey= this.getKey(key);
if(cacheClient.exists(tempKey)){
cacheClient.del(tempKey);
return null;
@Override
public void clear() throws CacheException {}
@Override
public int size() {
//@TODO
return 20;
@Override
public Set<K> keys() {
return null;
@Override
public Collection<V> values() {
Set keys = this.keys();
List<V> values = new ArrayList<>();
for (Object key : keys) {
values.add((V)cacheClient.getObject(this.getKey(key)));
return values;
* 根据名称获取
* @param key
* @return
private String getKey(Object key) {
return SecurityConsts.PREFIX_SHIRO_CACHE + JwtUtil.getClaim(key.toString(), SecurityConsts.ACCOUNT);
//shiro工具类
public class ShiroKit {
public final static String hashAlgorithmName = "MD5";
//循环次数
public final static int hashIterations = 1024;
* shiro密码加密工具类
* @param credentials 密码
* @param saltSource 密码盐
* @return
public static String md5(String credentials, String saltSource) {
ByteSource salt = new Md5Hash(saltSource);
return new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations).toString();
5. 登录
@Controller
@RequestMapping(value="/user")
public class LoginController {
@Autowired
IBpUserService bpUserService;
* @param user
* @return
@SuppressWarnings("unchecked")
@RequestMapping(value="/login")
@ResponseBody
public Result login(HttpServletResponse response,@RequestBody User user) {
return bpUserService.login(user,response);
//Service类
@Service
public class BpUserServiceImpl extends ServiceImpl<BpUserMapper, BpUser> implements IBpUserService {
@Autowired
CacheClient CacheClient;
* 用户登录
* @param user
* @return
@Override
public Result login(User user, HttpServletResponse response) {
Assert.notNull(user.getUsername(), "用户名不能为空");
Assert.notNull(user.getPassword(), "密码不能为空");
BpUser userBean = this.findUserByAccount(user.getUsername());
if(userBean==null){
return new Result(false, "用户不存在", null, Constants.PASSWORD_CHECK_INVALID);
//域账号直接提示账号不存在
if ("1".equals(userBean.getDomainFlag())) {
return new Result(false, "账号不存在", null, Constants.PASSWORD_CHECK_INVALID);
String encodePassword = ShiroKit.md5(user.getPassword(), SecurityConsts.LOGIN_SALT);
if (!encodePassword.equals(userBean.getPassword())) {
return new Result(false, "用户名或密码错误", null, Constants.PASSWORD_CHECK_INVALID);
//账号是否锁定
if ("0".equals(userBean.getStatus())) {
return new Result(false, "该账号已被锁定", null, Constants.PASSWORD_CHECK_INVALID);
//验证成功后处理
this.loginSuccess(userBean.getAccount(),response);
//登录成功
return new Result(true, "登录成功", null ,Constants.TOKEN_CHECK_SUCCESS);
* 登录后更新缓存,生成token,设置响应头部信息
* @param account
* @param response
private void loginSuccess(String account, HttpServletResponse response){
String currentTimeMillis = String.valueOf(System.currentTimeMillis());
// 清除可能存在的Shiro权限信息缓存
String tokenKey=SecurityConsts.PREFIX_SHIRO_CACHE + account;
if (cacheClient.exists(tokenKey)) {
cacheClient.del(tokenKey);
//更新RefreshToken缓存的时间戳
String refreshTokenKey= SecurityConsts.PREFIX_SHIRO_REFRESH_TOKEN + account;
if (cacheClient.exists(refreshTokenKey)) {
cacheClient.set(refreshTokenKey, currentTimeMillis, jwtProperties.getRefreshTokenExpireTime()*60*60l);
}else{
cacheClient.set(refreshTokenKey, currentTimeMillis, jwtProperties.getRefreshTokenExpireTime()*60*60l);
//生成token
JSONObject json = new JSONObject();
String token = JwtUtil.sign(account, currentTimeMillis);
json.put("token",token );
//写入header
response.setHeader(SecurityConsts.REQUEST_AUTH_HEADER, token);
response.setHeader("Access-Control-Expose-Headers", SecurityConsts.REQUEST_AUTH_HEADER);
登录成功后,我们在生成Token的同时,将当前时间戳以RefreshToken为Key存入Redis,用于Token过期时的校验及刷新。
当我们在业务中需要访问上下文用户时,可以这样获取:
UserContext.getCurrentUser().getAccount()
public class SystemLogoutFilter extends LogoutFilter {
private static final Logger logger = LoggerFactory.getLogger(SystemLogoutFilter.class);
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
Subject subject = getSubject(request, response);
try {
subject.logout();
} catch (Exception ex) {
logger.error("退出登录错误",ex);
this.writeResult(response);
//不执行后续的过滤器
return false;
private void writeResult(ServletResponse response){
//响应Json结果
PrintWriter out = null;
try {
out = response.getWriter();
Result result = new Result(true,null,null,Constants.TOKEN_CHECK_SUCCESS);
out.append(JSON.toJSONString(result));
} catch (IOException e) {
logger.error("返回Response信息出现IOException异常:" + e.getMessage());
} finally {
if (out != null) {
out.close();
<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>
<!--JWT-->
<dependency>
<groupid>com.auth0</groupid>
<artifactid>java-jwt</artifactid>
<version>3.4.1</version>
</dependency>
<!--Redis-->
<dependency>
<groupid>redis.clients</groupid>
<artifactid>jedis</artifactid>
<version>2.9.0</version>
</dependency>
Filter类里面的isAccessAllowed方法,为什么isLoginAttempt(request, response)验证为flse也返回true呢,header里面没有Token
refreshTokenCacheKey = SecurityConsts.PREFIX_SHIRO_REFRESH_TOKEN + account
refreshTokenCacheKey不会为null,cacheClient是你的Redis工具类,如果你的exists方法是static的应该不会空指针,
不是static,那你的cacheClient就应该是需要注入的,用@Autowired标记下。
我是真是服了,为什么我的jwt过滤器不能注入redis工具类?@autowired根本不能注入,即便在类上加@Component注解依然是null。非spring管理的类调用spring容器的bean像你这样会报空指针异常
因为这个JWTfilter 是以Bean的方式注入的 这个时候SpringBoot会自动注入这个Filter 并且在shiro默认的过滤器之前;
1.导致shiro的白名单全部失效,解决方式是可以在过滤器中手动判断请求是否在白名单中,这样太愚蠢了,所以我没有用.
2.但是如果不以Bean的方式注入 这个时候再JwtFilter这个类里面使用注解是无法注入bean的,不过使用SpringContext工具类可以获取到,我目前用的是这种方式
本文采用Token过期后刷新,但是后面已经改为Token有效期内刷新的方案。
Token生成时已经包含了过期时间,因此拿到Token后可以直接判断是否过期。
请参考后续文章:
采用JWT有效期内刷新Token方案,解决并发请求问题
token超时之后程序没走到refreshToken里,发现是Throwable throwable = e.getCause(); throwable 的类型是AuthenticationException,else if (throwable != null && throwable instanceof TokenExpiredException)是false,博主这里是怎么搞得?
realm中的doGetAuthenticationInfo这里主要用于验证token的有效性。登录时的token是新生成的,因此没有调用subject.login方法。当然你也可以调用:
Subject subject = SecurityUtils.getSubject();
AuthenticationToken token= new JwtToken(strToken);
subject.login(token);
doGetAuthorizationInfo 不知道你说的报错是为什么报错?父类中的onAccessDenied方法中倒是会再次调用executeLogin,而我们在JwtFilter中只重写了isAccessAllowed,并没有重写onAccessDenied,是会导致executeLogin被重新调用。我重写了onAccessDenied,代码已经提交,你可以重新pull测试下。
大佬,我在方法上加@RequiresAuthentication这个注解可以正常使用,但是如果使用@RequiresPermissions("system:role:list")这种的就会报异常,求指导啊
admin/111111
项目前端地址:https://github.com/sunnj/story-admin-console
postman只需要在构造请求参数时在请求头header中带上名为Authorization的Token内容即可。
您好,前台下载运行报了这个错:
PS D:\java2019\react-devtools-master> npm run dev
npm ERR! missing script: dev
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\admin\AppData\Roaming\npm-cache_logs\2019-09-09T14_07_47_230Z-debug.log
不好意思,我执行了的,不过报的这些信息(我重又执行了一次)
D:\java2019\react-devtools-master>npm install
npm WARN [email protected] requires a peer of ajv@>=5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: [email protected] (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for [email protected]: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
audited 32464 packages in 8.857s
found 76 vulnerabilities (66 low, 7 moderate, 2 high, 1 critical)
run npm audit fix
to fix them, or npm audit
for details
再执行 npm audit fix 一直没有反应。
您好,后台访问http://localhost:9430/
出现 欢迎来到 STORY-ADMIN 页面。我又重新下载了前端。运行后,前端url:http://localhost:9428/login的登录界面也出来了,用户和密码是系统默认的。但是点击登录出现提示信息:Request failed with status code 400。
请看看是为什么?
你好,我们重写了缓存管理器,是不是每次进来先去缓存里面查找对应的权限角色信息,没有的话再去数据库查?每次我从redis中查询时都会报错,提示Illegal character,能帮我解答一下吗
配合Swagger怎么使用啊,我设定了filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger-resources", "anon");
filterChainDefinitionMap.put("/swagger-resources/", "anon");
filterChainDefinitionMap.put("/v2/api-docs", "anon");
filterChainDefinitionMap.put("/webjars/springfox-swagger-ui/", "anon");都没有用啊,提示我401权限错误
SwaggerCofing类继承WebMvcConfigurationSupport类添加addResourceHandlers方法
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
application.yml 增加配置
- key: /swagger-ui.html
value: anon
- key: /swagger-resources/**
value: anon
- key: /v2/api-docs/**
value: anon
- key: /webjars/**
value: anon
谢谢大佬,我在调试使用的过程中又产生了个问题,携带token访问增删改查的api时,总是报错显示java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to org.apache.shiro.authz.AuthorizationInfo 是咋会事呢 不知道问题所在 ?求解答 ,谢谢啦
Filter类里面的isAccessAllowed方法,为什么isLoginAttempt(request, response)验证为flse也返回true呢,header里面没有Token
这段代码适用的场景是已登录和未登录都能看到页面但显示内容不同时,这里都返回true。
如果未登录需要拒绝访问,可以直接返回false。
代码已经做了变更,与本文稍有不同,请参考github代码,文末有仓库地址。
楼主你好,我想问一下,如果这样实现我是不是每次访问的时候都会进入自定义的过滤器,进入过滤器之后都需要在执行executeLogin进行登录?
如果每次访问都要登录,哪效率是不是有点太低了啊
这个得根据代码具体分析,shiro的配置是否生效?过滤器是否正确配置,或者其他的问题,或者可以跟踪下JwtFilter。
总之原因比较多,得具体分析,可以对比看下github仓库的代码。
找到原因了是yml中设置了 /** 为 anon
还请教一个问题,我是使用github仓库中最新的代码。这个tokenExpireTime和refreshCheckTime之间的联系有点模糊。
tokenExpireTime是token有效时间,refreshCheckTime是更新令牌时间,看你yml文件中的配置我的理解是:
token有效时间为1440分钟 ,refreshCheckTime更新令牌时间120分钟
是表示在这个1440分钟中每过120分钟更新一个token吗?
如果是那是用什么去更新的?更新后原有token还有效吗?原有token有效时间有1440分钟