实现前后端分离模式的登录接口
cv大魔王 2022-09-05 SpringSecurity 分享
现在都是前后端分离的开发方式,我们需要自定义登陆接口,然后让SpringSecurity对这个接口放行,让用户访问这个接口的时候不用登录也能访问。
# 引入jwt工具生成Token
前后端分离的开发模式下,后端使用jwt生成Token,前端登录获取Token,我们使用Hutool工具包的JWT工具 (opens new window)生成Token
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.5</version>
</dependency>
生成Token
// 密钥
byte[] key = "1234567890".getBytes();
String token = JWT.create()
.setPayload("id", "1")
.setPayload("username", "user1")
.setPayload("admin", true)
.setKey(key)
.sign();
解析Token
String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9……………………";
JWT jwt = JWT.of(rightToken);
jwt.getPayload("id");// 1
jwt.getPayload("username");// user1
# 开发JWT工具类
package com.xk857.jenkinsdemo.utils;
import cn.hutool.jwt.JWT;
import com.xk857.jenkinsdemo.domain.LoginUser;
import com.xk857.jenkinsdemo.domain.User;
import java.util.Date;
public class JwtUtils {
// 密钥
private static final byte[] TOKEN_KEY = "xk857.com".getBytes();
// token过期时间一周
private static final long EXPIRE = 60000 * 60 * 24 * 7;
/**
* 根据用户信息,生成令牌
* @param loginUser 用户对象
* @return token
*/
public static String geneJsonWebToken(LoginUser loginUser) {
// 默认使用HS256加密算法
return JWT.create()
.setPayload("id", loginUser.getUser().getId())
.setPayload("username", loginUser.getUser().getUsername())
.setPayload("nickName", loginUser.getUser().getNickName())
.setIssuedAt(new Date())
.setExpiresAt(new Date(System.currentTimeMillis() + EXPIRE))
.setKey(TOKEN_KEY).sign();
}
/**
* 解析Token
*/
public static User checkJWT(String token) {
JWT jwt = JWT.of(token);
User user = new User();
user.setId((String) jwt.getPayload("id"));
user.setUsername((String) jwt.getPayload("username"));
user.setNickName((String) jwt.getPayload("nickName"));
return user;
}
}
# 更改SpringSecurity默认配置
SpringSecurity默认开启csrf,我们因为是前后端分离开发,所以要把这个关闭,并允许登录接口匿名访问,另外还需要注入AuthenticationManager对象到Bean中,开发登录接口需要调用其方法。SpringBoot2.7.X写法如下:
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/user/login").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
SpringBoot2.7.X之前的写法如下:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/user/login").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
# 开发登录接口生成Token
@RestController
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@PostMapping("/user/login")
public Map<String,String> login(@RequestBody LoginParam param) {
// 使用SpringSecurity登录认证,通用写法
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(param.getUsername(),param.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if(Objects.isNull(authenticate)){
throw new RuntimeException("用户名或密码错误");
}
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
// 生成token
String token = JwtUtils.geneJsonWebToken(loginUser);
Map<String,String> map = new HashMap<>();
map.put("token",token);
return map;
}
}
# 撰写认证过滤器
我们需要自定义一个过滤器,这个过滤器会去获取请求头中的token,对token进行解析,然后封装Authentication对象存入SecurityContextHolder
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取token
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
//放行
filterChain.doFilter(request, response);
return;
}
//解析token,获取User对象封装成LoginUser对象
User user = JwtUtils.checkJWT(token);
LoginUser loginUser = new LoginUser(user);
//存入SecurityContextHolder
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request, response);
}
}
配置SpringSecurity,把token校验过滤器添加到过滤器链中
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/user/login").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
//把token校验过滤器添加到过滤器链中
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
# 登录接口测试
访问接口发现已经能正常访问了
# 携带Token访问测试接口
但是如果我们账号或密码故意输入错误,发现页面无反应。浏览器中访问测试接口提示403,403代表权限不足,我们想让它返回自定义的JSON数据该如何做呢?
评论区
暂无评论~~