这篇是接着上篇https://blog.csdn.net/zzxzzxhao/article/details/83381876
本篇主要目的是继续集成redis(实现refresh token和黑名单),logback实现日志管理
一般使用log4j的写法如下:
private final Logger logger = LoggerFactory.getLogger(YourClass.class);
然后使用lombok的@SLF4J标签可以很方便的省略这个步骤,直接用log就可以了(在上文有提及这个插件哦)
继续集成
logback的依赖不用引入了,可以看看jar依赖就知道了
ok直接进入配置环节,关于配置,你可以在application.yml里配置,但是只是简单的日志输出配置,假如我们想要:1.定时输出日志文件2.按日志级别输出该如何?所以我们另外配置一个logback-logback.xml来配置
%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) %cyan([%-50.50class]) : %boldYellow(%msg) %n
ERROR
DENY
ACCEPT
%d{yyyy-MM-dd HH:mm:ss} %-5level [%-50.50class] : %msg%n
E:\workSpace\springbootTest\seller\log\info.%d.log
ERROR
%d{yyyy-MM-dd HH:mm:ss} %-5level [%-50.50class] : %msg%n
E:\workSpace\springbootTest\seller\log\error.%d.log
里面的备注已经很清晰了,不多讲解了。(一些必要的地方加入日志,例如登录、注销什么的,后面代码会有)
测试
控制台
是不是好看一点了哈哈哈哈
查看日志文件是否生成
ok完成了
关于日志格式的问题,可以参考:
https://blog.csdn.net/qq496013218/article/details/69220907
https://blog.csdn.net/java_zone/article/details/54341029
https://blog.csdn.net/u013613428/article/details/51499552
org.springframework.boot
spring-boot-starter-data-redis
package com.deceen.common.utils;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
/**
* @author: zzx
* @date: 2018/10/23 14:24
* @description: 时间工具类
*/
public class DateUtil {
public final static SimpleDateFormat sdfTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static SimpleDateFormat sdf_date_format = new SimpleDateFormat("yyyy-MM-dd");
/**
* 获取当前时间的YYYY-MM-DD HH:mm:ss格式
*
* @return
*/
public static String getTime() {
return sdfTime.format(new Date());
}
/**
* 日期比较,如果s>=e 返回true 否则返回false
* @param s
* @param e
* @return
*/
public static boolean compareDate(String s, String e) {
if(fomatDate(s)==null||fomatDate(e)==null){
return false;
}
// return fomatDate(s).getTime() >=fomatDate(e).getTime();
return s.compareTo(e)>0;
}
/**
* 格式化日期
* @param date
* @return
*/
public static Date fomatDate(String date) {
DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
try {
return fmt.parse(date);
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}
/**
* 获取当前时间的后i天
* @param i
* @return
*/
public static String getAddDay(int i){
String currentTime = DateUtil.getTime();
GregorianCalendar gCal = new GregorianCalendar(
Integer.parseInt(currentTime.substring(0, 4)),
Integer.parseInt(currentTime.substring(5, 7)) - 1,
Integer.parseInt(currentTime.substring(8, 10)));
gCal.add(GregorianCalendar.DATE, i);
return sdf_date_format.format(gCal.getTime());
}
/**
* 获取当前时间的后i天
* 精确到秒
* @param i
* @return
*/
public static String getAddDayTime(int i){
Date date = new Date(System.currentTimeMillis()+i*24*60*60*1000);
return sdfTime.format(date);
}
/**
* 获取当前时间的+多少秒
* 精确到秒
* @param i
* @return
*/
public static String getAddDaySecond(int i){
Date date = new Date(System.currentTimeMillis()+i*1000);
return sdfTime.format(date);
}
}
package com.deceen.common.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author: zzx
* @date: 2018/10/23 10:31
* @description: redis工具类
*/
@Component
public class RedisUtil {
@Value("${token.expirationSeconds}")
private int expirationSeconds;
/*常量,各种实现方式都行,这里读取application.yml*/
@Value("${token.validTime}")
private int validTime;
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 查询key,支持模糊查询
*
* @param key 传过来时key的前后端已经加入了*,或者根据具体处理
* */
public Set keys(String key){
return redisTemplate.keys(key);
}
/**
* 字符串获取值
* @param key
* */
public Object get(String key){
return redisTemplate.opsForValue().get(key);
}
/**
* 字符串存入值
* 默认过期时间为2小时
* @param key
* */
public void set(String key, String value){
redisTemplate.opsForValue().set(key,value, 7200,TimeUnit.SECONDS);
}
/**
* 字符串存入值
* @param expire 过期时间(毫秒计)
* @param key
* */
public void set(String key, String value,Integer expire){
redisTemplate.opsForValue().set(key,value, expire,TimeUnit.SECONDS);
}
/**
* 删出key
* 这里跟下边deleteKey()最底层实现都是一样的,应该可以通用
* @param key
* */
public void delete(String key){
redisTemplate.opsForValue().getOperations().delete(key);
}
/**
* 添加单个
* 默认过期时间为两小时
* @param key key
* @param filed filed
* @param domain 对象
*/
public void hset(String key,String filed,Object domain){
redisTemplate.opsForHash().put(key, filed, domain);
}
/**
* 添加单个
* @param key key
* @param filed filed
* @param domain 对象
* @param expire 过期时间(毫秒计)
*/
public void hset(String key,String filed,Object domain,Integer expire){
redisTemplate.opsForHash().put(key, filed, domain);
redisTemplate.expire(key, expire,TimeUnit.SECONDS);
}
/**
* 添加HashMap
*
* @param key key
* @param hm 要存入的hash表
*/
public void hset(String key, HashMap hm){
redisTemplate.opsForHash().putAll(key,hm);
}
/**
* 如果key存在就不覆盖
* @param key
* @param filed
* @param domain
*/
public void hsetAbsent(String key,String filed,Object domain){
redisTemplate.opsForHash().putIfAbsent(key, filed, domain);
}
/**
* 查询key和field所确定的值
*
* @param key 查询的key
* @param field 查询的field
* @return HV
*/
public Object hget(String key,String field) {
return redisTemplate.opsForHash().get(key, field);
}
/**
* 查询该key下所有值
*
* @param key 查询的key
* @return Map
*/
public Object hget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 删除key下所有值
*
* @param key 查询的key
*/
public void deleteKey(String key) {
redisTemplate.opsForHash().getOperations().delete(key);
}
/**
* 判断key和field下是否有值
*
* @param key 判断的key
* @param field 判断的field
*/
public Boolean hasKey(String key,String field) {
return redisTemplate.opsForHash().hasKey(key,field);
}
/**
* 判断key下是否有值
*
* @param key 判断的key
*/
public Boolean hasKey(String key) {
return redisTemplate.opsForHash().getOperations().hasKey(key);
}
/**
* 判断此token是否在黑名单中
* @param token
* @return
*/
public Boolean isBlackList(String token){
return hasKey("blacklist",token);
}
/**
* 将token加入到redis黑名单中
* @param token
*/
public void addBlackList(String token){
hset("blacklist", token,"true");
}
/**
* 查询token下的刷新时间
*
* @param token 查询的key
* @return HV
*/
public Object getTokenValidTimeByToken(String token) {
return redisTemplate.opsForHash().get(token, "tokenValidTime");
}
/**
* 查询token下的刷新时间
*
* @param token 查询的key
* @return HV
*/
public Object getUsernameByToken(String token) {
return redisTemplate.opsForHash().get(token, "username");
}
/**
* 查询token下的刷新时间
*
* @param token 查询的key
* @return HV
*/
public Object getIPByToken(String token) {
return redisTemplate.opsForHash().get(token, "ip");
}
/**
* 查询token下的过期时间
*
* @param token 查询的key
* @return HV
*/
public Object getExpirationTimeByToken(String token) {
return redisTemplate.opsForHash().get(token, "expirationTime");
}
public void setTokenRefresh(String token,String username,String ip){
//刷新时间
Integer expire = validTime*24*60*60*1000;
hset(token, "tokenValidTime",DateUtil.getAddDayTime(validTime),expire);
hset(token, "expirationTime",DateUtil.getAddDaySecond(expirationSeconds),expire);
hset(token, "username",username,expire);
hset(token, "ip",ip,expire);
}
}
@Value("${token.expirationSeconds}")的意思是读取配置中的token.expirationSeconds,可以在application.yml里写入,无非相当于常量
package com.deceen.common.utils;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* @author: zzx
* @date: 2018/10/22 15:25
* @description: 自定义访问地址工具类
* 获取请求的ip地址等信息
*/
@Component
public class AccessAddressUtil {
/**
* 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址,
* 参考文章: http://developer.51cto.com/art/201111/305181.htm
*
* 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢?
* 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
*
* 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130,
* 192.168.1.100
*
* 用户真实IP为: 192.168.1.110
* @param request
* @return
*/
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
因为生成token加入ip验证,所以需要这个
package com.deceen.common.utils;
import java.util.Iterator;
import java.util.Map;
/**
* @author: zzx
* @date: 2018/10/22 16:20
* @description: 集合工具类
*
*/
public class CollectionUtil {
/**
* @param map 取值的集合
* @param key 所想取值的集合的key
* @return 返回key对应的value
*/
public static String getMapValue(Map map,String key){
String result = null;
if(map != null){
Iterator iterable = map.keySet().iterator();
while (iterable.hasNext()){
Object object = iterable.next();
if(key.equals(object))
if(map.get(object) != null)
result = map.get(object).toString();
}
}
return result;
}
}
package com.deceen.common.utils;
import java.util.UUID;
/**
* @author: zzx
* @date: 2018/10/23 9:20
* @description: 字符串工具类
*/
public class StringUtil {
/**
* 判断是否是空字符串null和""
*
* @param str
* @return
*/
public static boolean isEmpty(String str){
if (str != null && !str.equals("")) {
return false;
}
return true;
}
/**
* 判断两个字符串是否相等 如果都为null则判断为相等,一个为null另一个not null则判断不相等 否则如果s1=s2则相等
*
* @param s1
* @param s2
* @return
*/
public static boolean equals(String s1, String s2) {
if (StringUtil.isEmpty(s1) && StringUtil.isEmpty(s2)) {
return true;
} else if (!StringUtil.isEmpty(s1) && !StringUtil.isEmpty(s2)) {
return s1.equals(s2);
}
return false;
}
/**
* 生成uuid
* @return
*/
public static String getUUID(){
return UUID.randomUUID().toString();
}
}
所以JwtTokenUtil进行修改
package com.deceen.common.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Date;
import java.util.Map;
/**
* @author: zzx
* @date: 2018/10/16 9:06
* @description: jwt生成token
*/
public class JwtTokenUtil {
@Autowired
private static RedisUtil redisUtil;
// 寻找证书文件
private static InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("jwt.jks"); // 寻找证书文件
private static PrivateKey privateKey = null;
private static PublicKey publicKey = null;
static { // 将证书文件里边的私钥公钥拿出来
try {
KeyStore keyStore = KeyStore.getInstance("JKS"); // java key store 固定常量
keyStore.load(inputStream, "123456".toCharArray());
privateKey = (PrivateKey) keyStore.getKey("jwt", "123456".toCharArray()); // jwt 为 命令生成整数文件时的别名
publicKey = keyStore.getCertificate("jwt").getPublicKey();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 生成token
* @param subject (主体信息)
* @param expirationSeconds 过期时间(秒)
* @param claims 自定义身份信息
* @return
*/
public static String generateToken(String subject, int expirationSeconds, Map claims) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setExpiration(new Date(System.currentTimeMillis() + expirationSeconds * 1000))
// .signWith(SignatureAlgorithm.HS512, salt) // 不使用公钥私钥
.signWith(SignatureAlgorithm.RS256, privateKey)
.compact();
}
/**
* @author: zzx
* @date: 2018-10-19 09:10
* @deprecation: 解析token,获得subject中的信息
*/
public static String parseToken(String token, String salt) {
String subject = null;
try {
/*Claims claims = Jwts.parser()
// .setSigningKey(salt) // 不使用公钥私钥
.setSigningKey(publicKey)
.parseClaimsJws(token).getBody();*/
subject = getTokenBody(token).getSubject();
} catch (Exception e) {
}
return subject;
}
//获取token自定义属性
public static Map getClaims(String token){
Map claims = null;
try {
claims = getTokenBody(token);
}catch (Exception e) {
}
return claims;
}
// 是否已过期
public static boolean isExpiration(String expirationTime){
/*return getTokenBody(token).getExpiration().before(new Date());*/
//通过redis中的失效时间进行判断
String currentTime = DateUtil.getTime();
if(DateUtil.compareDate(currentTime,expirationTime)){
//当前时间比过期时间小,失效
return true;
}else{
return false;
}
}
private static Claims getTokenBody(String token){
return Jwts.parser()
.setSigningKey(publicKey)
.parseClaimsJws(token)
.getBody();
}
}
万事具备,继续
重写AjaxAuthenticationSuccessHandler这个类
package com.deceen.common.security;
import com.alibaba.fastjson.JSON;
import com.deceen.common.Enums.ResultEnum;
import com.deceen.common.utils.*;
import com.deceen.common.VO.ResultVO;
import com.deceen.demo.entity.SelfUserDetails;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* @author: zzx
* @date: 2018/10/15 16:12
* @description: 用户登录成功时返回给前端的数据
*/
@Component
@Slf4j
public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Value("${token.expirationSeconds}")
private int expirationSeconds;
@Value("${token.validTime}")
private int validTime;
@Autowired
private RedisUtil redisUtil;
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//获取请求的ip地址
String ip = AccessAddressUtil.getIpAddress(httpServletRequest);
Map map = new HashMap<>();
map.put("ip",ip);
SelfUserDetails userDetails = (SelfUserDetails) authentication.getPrincipal();
String jwtToken = JwtTokenUtil.generateToken(userDetails.getUsername(), expirationSeconds, map);
//刷新时间
Integer expire = validTime*24*60*60*1000;
//获取请求的ip地址
String currentIp = AccessAddressUtil.getIpAddress(httpServletRequest);
redisUtil.setTokenRefresh(jwtToken,userDetails.getUsername(),currentIp);
log.info("用户{}登录成功,信息已保存至redis",userDetails.getUsername());
httpServletResponse.getWriter().write(JSON.toJSONString(ResultVO.result(ResultEnum.USER_LOGIN_SUCCESS,jwtToken,true)));
}
}
修改AjaxLogoutSuccessHandler这个类
package com.deceen.common.security;
import com.alibaba.fastjson.JSON;
import com.deceen.common.Enums.ResultEnum;
import com.deceen.common.VO.ResultVO;
import com.deceen.common.utils.DateUtil;
import com.deceen.common.utils.RedisUtil;
import com.deceen.demo.entity.SelfUserDetails;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author: zzx
* @date: 2018/10/16 9:59
* @description: 登出成功
*/
@Component
@Slf4j
public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {
@Autowired
private RedisUtil redisUtil;
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
String authHeader = httpServletRequest.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
final String authToken = authHeader.substring("Bearer ".length());
//将token放入黑名单中
redisUtil.hset("blacklist", authToken, DateUtil.getTime());
log.info("token:{}已加入redis黑名单",authToken);
}
httpServletResponse.getWriter().write(JSON.toJSONString(ResultVO.result(ResultEnum.USER_LOGOUT_SUCCESS,true)));
}
}
更改JwtAuthenticationTokenFilter
package com.deceen.common.filters;
import com.alibaba.fastjson.JSON;
import com.deceen.common.Enums.ResultEnum;
import com.deceen.common.VO.ResultVO;
import com.deceen.common.utils.*;
import com.deceen.demo.service.SelfUserDetailsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
//import com.deceen.common.utils.JwtTokenUtil;
/**
* @author: zzx
* @date: 2018/10/15 17:30
* @description: 确保在一次请求只通过一次filter,而不需要重复执行
*/
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Value("${token.expirationSeconds}")
private int expirationSeconds;
@Value("${token.validTime}")
private int validTime;
@Autowired
SelfUserDetailsService userDetailsService;
@Autowired
RedisUtil redisUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
//获取请求的ip地址
String currentIp = AccessAddressUtil.getIpAddress(request);
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String authToken = authHeader.substring("Bearer ".length());
String username = JwtTokenUtil.parseToken(authToken, "_secret");
String ip = CollectionUtil.getMapValue(JwtTokenUtil.getClaims(authToken), "ip");
//进入黑名单验证
if (redisUtil.isBlackList(authToken)) {
log.info("用户:{}的token:{}在黑名单之中,拒绝访问",username,authToken);
response.getWriter().write(JSON.toJSONString(ResultVO.result(ResultEnum.TOKEN_IS_BLACKLIST, false)));
return;
}
//判断token是否过期
/*
* 过期的话,从redis中读取有效时间(比如七天登录有效),再refreshToken(根据以后业务加入,现在直接refresh)
* 同时,已过期的token加入黑名单
*/
if (redisUtil.hasKey(authToken)) {//判断redis是否有保存
String expirationTime = redisUtil.hget(authToken,"expirationTime").toString();
if (JwtTokenUtil.isExpiration(expirationTime)) {
//获得redis中用户的token刷新时效
String tokenValidTime = (String) redisUtil.getTokenValidTimeByToken(authToken);
String currentTime = DateUtil.getTime();
//这个token已作废,加入黑名单
log.info("{}已作废,加入黑名单",authToken);
redisUtil.hset("blacklist", authToken, DateUtil.getTime());
if (DateUtil.compareDate(currentTime, tokenValidTime)) {
//超过有效期,不予刷新
log.info("{}已超过有效期,不予刷新",authToken);
response.getWriter().write(JSON.toJSONString(ResultVO.result(ResultEnum.LOGIN_IS_OVERDUE, false)));
return;
} else {//仍在刷新时间内,则刷新token,放入请求头中
String usernameByToken = (String) redisUtil.getUsernameByToken(authToken);
username = usernameByToken;//更新username
ip = (String) redisUtil.getIPByToken(authToken);//更新ip
//获取请求的ip地址
Map map = new HashMap<>();
map.put("ip", ip);
String jwtToken = JwtTokenUtil.generateToken(usernameByToken, expirationSeconds, map);
//更新redis
Integer expire = validTime * 24 * 60 * 60 * 1000;//刷新时间
redisUtil.setTokenRefresh(jwtToken,usernameByToken,ip);
//删除旧的token保存的redis
redisUtil.deleteKey(authToken);
//新的token保存到redis中
redisUtil.setTokenRefresh(jwtToken,username,ip);
log.info("redis已删除旧token:{},新token:{}已更新redis",authToken,jwtToken);
authToken = jwtToken;//更新token,为了后面
response.setHeader("Authorization", "Bearer " + jwtToken);
}
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
/*
* 加入对ip的验证
* 如果ip不正确,进入黑名单验证
*/
if (!StringUtil.equals(ip, currentIp)) {//地址不正确
log.info("用户:{}的ip地址变动,进入黑名单校验",username);
//进入黑名单验证
if (redisUtil.isBlackList(authToken)) {
log.info("用户:{}的token:{}在黑名单之中,拒绝访问",username,authToken);
response.getWriter().write(JSON.toJSONString(ResultVO.result(ResultEnum.TOKEN_IS_BLACKLIST, false)));
return;
}
//黑名单没有则继续,如果黑名单存在就退出后面
}
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (userDetails != null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
filterChain.doFilter(request, response);
}
}
关于redis服务器的搭建,有很多教程,我这里就不多赘述了
相关application.yml配置更新:
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost/springboot?characterEncoding=utf-8&useSSl=false
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#监控统计拦截的filters
filters: stat,wall,log4j
#druid配置
#配置初始化大小/最小/最大
initialSize: 5
minIdle: 5
maxActive: 20
#获取连接等待超时时间
maxWait: 60000
#间隔多久进行一次检测,检测需要关闭的空闲连接
timeBetweenEvictionRunsMillis: 60000
#一个连接在池中最小生存的时间
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
#打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
poolPreparedStatements: false
maxPoolPreparedStatementPerConnectionSize: 20
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties:
druid:
stat:
mergeSql: true
slowSqlMillis: 5000
http:
encoding:
charset: utf-8
force: true
enabled: true
redis:
host: 127.0.0.1
port: 6379
password:
#mybatis是独立节点,需要单独配置
mybatis:
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: com.deceen.demo.entity
configuration:
map-underscore-to-camel-case: true
server:
context-path: /demo
port: 8080
tomcat:
uri-encoding: utf-8
#自定义参数,可以迁移走
token:
#token失效时间(不是refreshToken)(这是自定义的)(秒)
expirationSeconds: 300
#默认7天登录有效(根据业务更改)
validTime: 7
看结果如何:
postman发送请求:
用redis desktop manager查看redis:
正确!
(即超过我设定五分钟有效期,但仍在7天刷新期)(直接修改redis数据,简单粗暴,一切为了测试):
点击headers,对比Authorization 中的token,可以知道token已刷新,那么我们看控制台:
太长了,我截取一部分吧
2018-10-26 15:39:11 INFO [n.common.security.AjaxAuthenticationSuccessHandler] : 用户zzx1登录成功,信息已保存至redis
2018-10-26 15:43:15 INFO [deceen.common.filters.JwtAuthenticationTokenFilter] : eyJhbG..TNjpw已作废,加入黑名单
2018-10-26 15:43:15 INFO [deceen.common.filters.JwtAuthenticationTokenFilter] : redis已删除旧token:eyJhbGc..tdWcs,新token:eyJhbG...tdWcs已更新redis
ps:这里有个错误,控制台打印的时候新旧token没传好参数,改一下filter中的就好
查看redis:
旧token回收到黑名单之中,新token插入到redis,完成
再继续测试
查看redis,对比token是否正确加入黑名单。
再用这个尚未失效的token(即仍在五分钟有效期内请求)
完成
github项目地址:https://github.com/zzxzzxhao/springboot-springsecurity-ultimate-
==================2019.06.06=====================
分享一下当时我查阅的博客;
https://blog.csdn.net/u013435893/article/details/79596628
https://blog.csdn.net/larger5/article/details/81063438
https://blog.csdn.net/ech13an/article/details/80779973?utm_source=blogxgwz0
https://www.cnblogs.com/softidea/p/7068149.html
https://blog.csdn.net/qq_34912469/article/details/75666506
https://www.cnblogs.com/stulzq/p/9678501.html#commentform
https://segmentfault.com/a/1190000013151506
https://segmentfault.com/q/1010000010003323