接着上一次我们这一次将整合spring security 安全框架接下来分析我们项目使用框架的流程
流程说明:
- 客户端发起一个请求,进入 Security 过滤器链。
- 当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler ,如果登出成功则到 logoutSuccessHandler 登出成功处理。如果不是登出路径则直接进入下一个过滤器。
- 当到 UsernamePasswordAuthenticationFilter 的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作,如果登录失败则到 AuthenticationFailureHandler ,登录失败处理器处理,如果登录成功则到 AuthenticationSuccessHandler 登录成功处理器处理,如果不是登录请求则不进入该过滤器。
- 进入认证BasicAuthenticationFilter进行用户认证,成功的话会把认证了的结果写入到SecurityContextHolder中SecurityContext的属性authentication上面。如果认证失败就会交给AuthenticationEntryPoint认证失败处理类,或者抛出异常被后续ExceptionTranslationFilter过滤器处理异常,如果是AuthenticationException就交给AuthenticationEntryPoint处理,如果是AccessDeniedException异常则交给AccessDeniedHandler处理。
- 当到 FilterSecurityInterceptor 的时候会拿到 uri ,根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层,否则到 AccessDeniedHandler 鉴权失败处理器处理。
下面我们需要了解几个组件
- LogoutFilter - 登出过滤器
- logoutSuccessHandler - 登出成功之后的操作类
- UsernamePasswordAuthenticationFilter - from提交用户名密码登录认证过滤器
- AuthenticationFailureHandler - 登录失败操作类
- AuthenticationSuccessHandler - 登录成功操作类
- BasicAuthenticationFilter - Basic身份认证过滤器
- SecurityContextHolder - 安全上下文静态工具类
- AuthenticationEntryPoint - 认证失败入口
- ExceptionTranslationFilter - 异常处理过滤器
- AccessDeniedHandler - 权限不足操作类
- FilterSecurityInterceptor - 权限判断拦截器、出口
?这时候我们需要加入security的依赖就是上次注释的依赖然后启动redis运行项目是否引入成功
因为security内置了登录页,用户名为user,密码在启动项目的时候打印在了控制台。登录完成之后我们才可以正常访问接口因为每次启动密码都会变我们可以自己配置默认账户和密码
application.yml
?
spring:
security:
user:
name: user
password: 111111
?
用户认证:
首先我们来解决用户认证问题,分为首次登陆,和二次认证。
- 首次登录认证:用户名、密码和验证码完成登录
- 二次token认证:请求头携带Jwt进行身份认证
使用用户名密码来登录的,然后我们还想添加图片验证码,那么security给我们提供的UsernamePasswordAuthenticationFilter能使用吗?
首先security的所有过滤器都是没有图片验证码这回事的,看起来不适用了。其实这里我们可以灵活点,如果你依然想沿用自带的UsernamePasswordAuthenticationFilter,那么我们就在这过滤器之前添加一个图片验证码过滤器。当然了我们也可以通过自定义过滤器继承UsernamePasswordAuthenticationFilter,然后自己把验证码验证逻辑和认证逻辑写在一起,这也是一种解决方式。
我们这次解决方式是在UsernamePasswordAuthenticationFilter之前自定义一个图片过滤器CaptchaFilter,提前校验验证码是否正确,这样我们就可以使用UsernamePasswordAuthenticationFilter了,然后登录正常或失败我们都可以通过对应的Handler来返回我们特定格式的封装结果数据。
生成验证码?:
首先我们先生成验证码引用google的验证码生成器,我们配置一下图片验证码规则
com.rao.config.KaptchaConfig
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
@Configuration
public class KaptchaConfig {
@Bean
public DefaultKaptcha producer(){
Properties properties = new Properties();
properties.put("kaptcha.border", "no");
properties.put("kaptcha.textproducer.font.color", "black");
properties.put("kaptcha.textproducer.char.space", "4");
properties.put("kaptcha.image.height", "40");
properties.put("kaptcha.image.width", "120");
properties.put("kaptcha.textproducer.font.size", "30");
Config config = new Config(properties);
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
上面定义了图片的长宽字体颜色等自己可以调整
然后通过控制器提出生成验证码的方法:
首先写一个父类把全部service层注入到这个类
com.rao.controller.BaseController
@Autowired
HttpServletRequest request;
@Autowired
SysUserService sysUserService;
@Autowired
SysRoleService sysRoleService;
@Autowired
SysMenuService sysMenuService;
@Autowired
SysUserRoleService sysUserRoleService;
@Autowired
SysRoleMenuService sysRoleMenuService;
@Autowired
RedisUtil redisUtil;
/**
* 获取页码
* @return
*/
public Page getPage(){
int current= ServletRequestUtils.getIntParameter(request,"current",1);
int size= ServletRequestUtils.getIntParameter(request,"size",10);
return new Page(current,size);
}
因为前后端分离,我们禁用了session,所以我们把验证码放在了redis中,使用一个随机字符串作为key,并传送到前端,前端再把随机字符串和用户输入的验证码提交上来,这样我们就可以通过随机字符串获取到保存的验证码和用户的验证码进行比较了是否正确了。 然后因为图片验证码的方式,所以我们进行了encode,把图片进行了base64编码,这样前端就可以显示图片了。
所以我们写了一个工具类redisUtil:com.rao.utils.RedisUtil
package com.rao.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
//============================String=============================
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
//================================Map=================================
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
//============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//================有序集合 sort set===================
/**
* 有序set添加元素
*
* @param key
* @param value
* @param score
* @return
*/
public boolean zSet(String key, Object value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
public long batchZSet(String key, Set<ZSetOperations.TypedTuple> typles) {
return redisTemplate.opsForZSet().add(key, typles);
}
public void zIncrementScore(String key, Object value, long delta) {
redisTemplate.opsForZSet().incrementScore(key, value, delta);
}
public void zUnionAndStore(String key, Collection otherKeys, String destKey) {
redisTemplate.opsForZSet().unionAndStore(key, otherKeys, destKey);
}
/**
* 获取zset数量
* @param key
* @param value
* @return
*/
public long getZsetScore(String key, Object value) {
Double score = redisTemplate.opsForZSet().score(key, value);
if(score==null){
return 0;
}else{
return score.longValue();
}
}
/**
* 获取有序集 key 中成员 member 的排名 。
* 其中有序集成员按 score 值递减 (从大到小) 排序。
* @param key
* @param start
* @param end
* @return
*/
public Set<ZSetOperations.TypedTuple> getZSetRank(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
}
}
com.rao.controller.AuthController
@Autowired
private Producer producer;
@GetMapping("/captcha")
public Result captcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
String code = producer.createText();
String key = UUID.randomUUID().toString();
BufferedImage image = producer.createImage(code);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(image,"jpg",outputStream);
BASE64Encoder encoder = new BASE64Encoder();
String str="data:image/jpeg;base64,";
String base64Img=str+ encoder.encode(outputStream.toByteArray());
//存储到redis中
redisUtil.hset(Const.CAPTCHA_KEY,key,code,120);
log.info("验证码--{}-{}",key,code);
return Result.succ(MapUtil.builder()
.put("token",key)
.put("base64Img",base64Img)
.build()
);
建一个常量类com.rao.common.lang
public class Const {
public final static String CAPTCHA_KEY="captcha";
public final static Integer STATUS_ON=1;
public final static Integer STATUS_OFF=0;
public static final String DEFULT_PASSWORD ="888888";
public static final String DEFULT_AVATAR ="https://image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/5a9f48118166308daba8b6da7e466aab.jpg" ;
}
?验证码认证过滤器
图片验证码进行验证码是否正确
先建认证失败处理器com.rao.security.LoginFailureHandler
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream outputStream = response.getOutputStream();
Result result = Result.fail(
"Bad credentials".equals(exception.getMessage())?"用户名和密码不正确":exception.getMessage()
);
outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
再建验证码异常处理com.rao.comm.exception
public class CaptchaException extends AuthenticationException {
public CaptchaException(String msg) {
super(msg);
}
}
再建验证码过滤器com,rao.security.CaptchaFilter
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.rao.common.exception.CaptchaException;
import com.rao.common.lang.Const;
import com.rao.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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;
/**
* 图片验证过滤器,在登录过滤器前
*/
@Slf4j
@Component
public class CaptchaFilter extends OncePerRequestFilter {
private final String loginUrl="/login";
@Autowired
RedisUtil redisUtil;
@Autowired
LoginFailureHandler loginFailureHandler;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String url = request.getRequestURI();
if (loginUrl.equals(url)&&request.getMethod().equals("POST")){
log.info("获取到login连接,正在校验验证码---"+url);
try {
validate(request);
}catch (CaptchaException e){
log.info(e.getMessage());
//交给登录失败处理器
loginFailureHandler.onAuthenticationFailure(request,response,e);
}
}
filterChain.doFilter(request,response);
}
private void validate(HttpServletRequest request) {
String code = request.getParameter("code");
String token = request.getParameter("token");
if (StringUtils.isBlank(code)||StringUtils.isBlank(token)){
throw new CaptchaException("验证码不能为空");
}
if (!code.equals(redisUtil.hget(Const.CAPTCHA_KEY,token))){
throw new CaptchaException("验证码不正确");
}
//一次性使用
redisUtil.hdel(Const.CAPTCHA_KEY,token);
}
}
其实主要就是获取异常的消息,然后封装到Result,最后转成json返回给前端而已哈。 然后我们配置SecurityConfig
- com.markerhub.config.SecurityConfig
import com.rao.security.CaptchaFilter;
import com.rao.security.LoginFailureHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
LoginFailureHandler loginFailureHandler;
@Autowired
CaptchaFilter captchaFilter;
private static final String[] URL_WHITELIST = {
"/login",
"/logout",
"/captcha",
"/favicon.ico",
};
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.formLogin()
.failureHandler(loginFailureHandler)
.and()
.authorizeRequests()
.antMatchers(URL_WHITELIST).permitAll()
.anyRequest().authenticated()
//不会创建session
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class);
}
}
?用postman测试一下结果:
?得到验证码和token验证码在后台
?后五位
?
然后我们尝试登录,因为之前我们已经设置了用户名密码为user/111111,所以我们提交表单的时候再带上我们的token和验证码。
这时候我们就可以去提交表单了吗,其实还不可以,为啥?因为就算我们登录成功,security默认跳转到/链接,但是又会因为没有权限访问/,所有又会教你去登录,所以我们必须取消原先默认的登录成功之后的操作,根据我们之前分析的流程,登录成功之后会走AuthenticationSuccessHandler,因此在登录之前,我们先去自定义这个登录成功操作类这里我们用到jwtUtils工具类
import io.jsonwebtoken.*;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
@Data
@Component
@ConfigurationProperties(prefix = "markerhub.jwt")
public class JwtUtils {
private long expire;
private String secret;
private String header;
// 生成jwt
public String generateToken(String username) {
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime() + 1000 * expire);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(username)
.setIssuedAt(nowDate)
.setExpiration(expireDate)// 7天過期
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
// 解析jwt
public Claims getClaimByToken(String jwt) {
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(jwt)
.getBody();
} catch (Exception e) {
return null;
}
}
// jwt是否过期
public boolean isTokenExpired(Claims claims) {
return claims.getExpiration().before(new Date());
}
}
登录成功之后我们利用用户名生成jwt,jwtUtils这个工具类我就不贴代码了哈,去看我们项目源码,然后把jwt作为请求头返回回去,名称就叫Authorization哈。我们需要在配置文件中配置一些jwt的一些密钥信息:
markerhub:
jwt:
# 加密秘钥
secret: f4e2e52034348f86b67cde581c0f9eb5
# token有效时长,7天,单位秒
expire: 604800
header: Authorization
com.rao.security.LoginSuccessHandler?:
package com.rao.security;
import cn.hutool.json.JSONUtil;
import com.rao.common.lang.Result;
import com.rao.utils.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
JwtUtils jwtUtils;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream outputStream = response.getOutputStream();
String jwt = jwtUtils.generateToken(authentication.getName());
response.setHeader(jwtUtils.getHeader(),jwt);
Result result = Result.succ("");
outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
然后我们再security配置中添加上登录成功之后的操作类:
- com.markerhub.config.SecurityConfig
@Autowired
LoginSuccessHandler loginSuccessHandler;
...
# configure代码:
http.cors().and().csrf().disable()
.formLogin()
.failureHandler(loginFailureHandler)
.successHandler(loginSuccessHandler)
用postman测试一下:验证码在
?
?
|