原文地址
概述
在开始这篇文章前,博主默认你们已经对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
.
代码实现
后端实现
-
引入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
-
代码实现
1、在用户成功登录后下发Token
Spring Security
在做登录操作的时候允许我们添加我们的自己的登录成功处理器
和登录失败处理器
。这里我编写了自己的成功处理器
和失败处理器
。在成功处理器
中添加了生成JWT
的操作。代码如下:
//自定义的登录成功处理器
@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));
}
}
//自定义登录失败处理器
@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
进行校验。
//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
的配置中
@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
。
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;
});
},
//使用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)
})
源码地址
来源:oschina
链接:https://my.oschina.net/u/4445110/blog/3158938