SpringBoot、SpringSecurity、Vue整合JWT认证

概述

在开始这篇文章前,博主默认你们已经对Spring Boot、Spring Security、Vue以及JWT已经了解。这里对以上概念也不再赘述。下面先讲一下思路。

1、后端需要编写JWT生成处理和JWT解析认证处理。

2、前端填写用户名和密码发送登录请求。

3、经后端Spring Security登录认证成功后,由JWT生成器生成Token返回给前端。

4、前端拿到Token,在之后的请求中需要携带这个Token

5、后端编写JWT过滤器,对请求中的Token进行解析处理,解析成功通过,失败返回相应提示。

效果展示

hello按钮不需要登录,测试1测试2按钮需要登录才能访问。点击登录后会获取Token,下次发送请求携带这个Token.

源码地址

代码实现

后端实现

  • 引入依赖

1
2
3
4
5
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
  • 代码实现

1、在用户成功登录后下发Token

Spring Security在做登录操作的时候允许我们添加我们的自己的登录成功处理器登录失败处理器。这里我编写了自己的成功处理器失败处理器。在成功处理器中添加了生成JWT的操作。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//自定义的登录成功处理器
@Component("myLoginSuccessHandler")
public class MyLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(getClass());

@Autowired
private ObjectMapper objectMapper;

@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication) throws IOException, ServletException {
logger.info("登录成功!");

// 登录成功后设置JWT
String Token = Jwts.builder()
//设置token的信息
// .setClaims(claimsMap)
//将认证后的authentication写入token,验证时,直接验证它
.claim("authentication",authentication)
//设置主题
.setSubject("主题")
//过期时间
.setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 24 * 1000))
//加密方式
.signWith(SignatureAlgorithm.HS512, "MyJWTtest")
.compact();
httpServletResponse.addHeader("Authorization", "Mrain" + Token);
//要做的工作就是将Authentication以json的形式返回给前端。 需要工具类ObjectMapper,Spring已自动注入。
//设置返回类型
httpServletResponse.setContentType("application/json;charset=UTF-8");
Map<String, Object> tokenInfo = new HashMap<String, Object>();
tokenInfo.put("Authorization","Mrain" + Token);
//将token信息写入
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(tokenInfo));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//自定义登录失败处理器
@Component("myLoginFailureHandler")
public class MyLoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
/**
* ObjectMapper这个类是java中jackson提供的,主要是用来把对象转换成为一个json字符串返回到前端,
*/
@Autowired
private ObjectMapper objectMapper;

@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
//json形式返回
//服务器内部异常
response.setStatus(500);
//设置返回类型
response.setContentType("application/json;charset=UTF-8");
//将错误信息写入
response.getWriter().write(objectMapper.writeValueAsString(exception.getMessage()));
}
}

2、JWT拦截器

定义我们自己的JWT拦截器,在请求到达目标之前对Token进行校验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//JWT拦截器
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

private Logger logger = LoggerFactory.getLogger(getClass());

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
//获取JWT
String authHeader = request.getHeader("Authorization");
logger.info("--------->"+authHeader);
if (authHeader != null) {
// 解析token.
Claims claims = Jwts.parser()
.setSigningKey("MyJWTtest")
.parseClaimsJws(authHeader.replace("Mrain", ""))
.getBody();
//获取suject
// String subject = claims.getSubject();
// User user = (User) claims.get("user");
//获取过期时间
Date claimsExpiration = claims.getExpiration();
logger.info("过期时间"+claimsExpiration);
//判断是否过期
Date now = new Date();
if (now.getTime() > claimsExpiration.getTime()) {
throw new AuthenticationServiceException("凭证已过期,请重新登录!");
}
//获取保存在token中的登录认证成功的authentication,
// 利用UsernamePasswordAuthenticationToken生成新的authentication
// 放入到SecurityContextHolder,表示认证通过
Object tokenInfo = claims.get("authentication");
//通过com.alibaba.fastjson将其在转换。
Authentication toknAuthentication = JSONObject.parseObject(JSONObject.toJSONString(tokenInfo), Authentication.class);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(toknAuthentication.getPrincipal(),null,toknAuthentication.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
}

3、配置Spring Security的配置

两个处理器和我们的Jwt拦截器添加到Spring Security的配置中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyLoginSuccessHandler myLoginSuccessHandler;
@Autowired
private MyLoginFailureHandler myLoginFailureHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
/** JWT拦截器*/
JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter = new JwtAuthenticationTokenFilter();
/** 将JWT拦截器添加到UsernamePasswordAuthenticationFilter之前*/
http.addFilterBefore(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class);

http.formLogin()
.loginPage("/loginInfo")
.loginProcessingUrl("/login")
.successHandler(myLoginSuccessHandler)
.failureHandler(myLoginFailureHandler);
http.authorizeRequests()
.antMatchers("/hello","/login","/loginInfo","/logoutSuccess")
.permitAll()
.anyRequest()
.authenticated();
//访问 /logout 表示用户注销,并清空session
http.logout().logoutSuccessUrl("/logoutSuccess");
// 关闭csrf
http.csrf().disable();
http.cors();
}

/**
* 密码加盐加密
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
//Spring自带的每次会随机生成盐值,即使密码相同,加密后也不同
return new BCryptPasswordEncoder();
}
}

前端实现

前端实现很简单,就是登录成功后,将返回的token保存起来,以后每次访问请求头携带这个token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
login() {
this.$http
.post("/login", {
username: 1,
password: 1
})
.then(res => {
// 登录成功
console.log("登录成功!");
console.log(res.data);
/** 将Token保存到localStorage*/
const authorization = res.data.Authorization;
localStorage.token = authorization;
this.msg = authorization;
})
.catch(error => {
console.log("登录失败!");
console.log(error);
this.msg = error;
});
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//使用axios发送请求的设置
// 在发送请求之前做某件事
Axios.interceptors.request.use(config => {
// 设置以 form 表单的形式提交参数,如果以JSON的形式提交表单,可忽略
if(config.method === 'post'){
// JSON 转换为 FormData
const formData = new FormData()
Object.keys(config.data).forEach(key => formData.append(key, config.data[key]))
config.data = formData
}

//本案例中将token保存到了localStorage,将其添加到请求头
if (localStorage.token) {
config.headers.Authorization = localStorage.token
}
return config
},error =>{
alert("错误的传参", 'fail')
return Promise.reject(error)
})

源码地址

Xiuming Lee wechat
欢迎您扫一扫上面的微信公众号订阅,更多惊喜等着您哦!
-------------本文结束感谢您的阅读-------------