写在前面:
这个问题在网上一直找不到我想要的答案,是通过我自己不断试错最后才成功的,故写出来分享一下。这是我的第一篇分享博客,如果有哪里写的不好的地方,请多多见谅!
需求提出:
SpringSecurity自带的权限认证有很多种,有Form Login的形式,也有http basic的形式等等。我们之前的JSP项目是通过<form>标签表单提交来进行登录认证的,这时候使用SpringSecurity的Form Login认证是最适合的,并且我们也可以在配置类中可以使用HttpSecurity对象做一些自定义的配置(登录处理器,username参数名,password参数名等等),如下代码所示:
http.formLogin()
.loginProcessingUrl("/user/login")
.usernameParameter("account")
.passwordParameter("password")
.permitAll();
通过几行简简单单的配置我们就可以将用户认证处理器url修改成/user/login了。
但是在前后端分离的项目中,如果我们要实现登录认证,就要使用Token来保存用户信息,并且要自定义一个用于登录认证的过滤器,继承UsernamePasswordAuthenticationFilter这个类,重写SpringSecurity的认证逻辑。我用到的是JWT来生成Token,这里贴出我的认证代码:
/**
* 登录验证过滤器(只拦截管理员和商家的登录请求)
*
* @author Ambitious
* @date 2022/3/17 22:45
*/
@Slf4j
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {
private final RsaKeys rsaKeys;
private final AuthenticationManager authenticationManager;
public JwtLoginFilter(AuthenticationManager manager, RsaKeys rsaKeys) {
this.authenticationManager = manager;
this.rsaKeys = rsaKeys;
}
/**
* 获取用户的登录信息(账号,密码),并进行校验
* @param request 请求对象
* @param response 响应对象
* @return 认证管理器的认证结果
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
log.info("调用了自定义的登录认证过滤器");
// 获取登录参数
String account = request.getParameter("account");
String password = request.getParameter("password");
if(!StringUtils.hasText(account) || !StringUtils.hasText(password)) {
// 参数不合法
ResponseUtils.write(response, ResponseVO.fail(SysCode.UN_VALID_REQUEST_PARAMETER));
return null;
}
// 构造SpringSecurity登录token
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(account, password);
return this.authenticationManager.authenticate(token);
}
/**
* 用户认证成功,使用JWT生成Token返回
* @param request 请求对象
* @param response 响应对象
* @param chain 过滤器链
* @param authResult 认证结果
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
// 获取认证用户对象,去除敏感信息
User user = (User) authResult.getPrincipal();
user.setPassword(null);
// 生成Token
String authorization = JwtUtils.generateToken(user, rsaKeys.getPrivateKey());
// 存入响应头并返回
response.setHeader("Authorization", "Bearer " + authorization);
ResponseUtils.write(response, ResponseVO.ok(SysCode.LOGIN_SUCCESS));
}
/**
* 认证失败
* @param request 请求对象
* @param response 响应对象
* @param failed 失败原因
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
ResponseUtils.write(response, ResponseVO.fail(SysCode.BAD_AUTHENTICATE));
}
}
这样子配置之后,再在SpringSecurity配置类中使用http.addFilter加入我们自定义的这个过滤器就可以通过请求http://localhost:8080/login接口并且携带登录参数来进行登录认证了,认证成功之后会将Token设置在响应头中,然后前端拿到Token进行其他的请求。
那么,如果我不想要这个默认接口呢?我的接口规范就是要/api/**,直接使用/login的话有可能会给项目后期部署带来不必要的麻烦。
尝试解决这个问题
1. 老办法解决
最开始的时候,我觉得这个需求很容易实现,就是继续使用http.formLogin方法,配置loginProcessUrl为/api/user/login就可以达到我的目的了。可结果是什么呢:
它给我跳到了SpringSecurity内置的登录页面!!!!
后来经过分析我发现,只要我配置了http.formLogin这个规则,我自定义的过滤器就失效了。。它进了默认的UsernamePasswordAuthenticationFilter,并且我的登录用户参数名是account不是默认的username,导致在数据库中查找不到用户数据,这时就认定我认证失败,所以给我跳到了登录页面。。。。
2. 寻求百度解决
令我没有想到的是这么简单的一个需求,居然在百度找不到一篇文章能给出解决办法,,
不过在我耐心的修改搜索关键词之后,我还是找到了一篇比较接近我这个需求的办法,它说可以给自定义的这个过滤器设置一个filterProcessUrl就可以了,看这属性名,它不就是我要的东西么,我只要给自定义的过滤器设置一个路径就可以让登录请求进到这个过滤器了。于是还是在刚刚那个JwtLoginFIlter类中,我加入了以下方法:
@Override
public void setFilterProcessesUrl(String filterProcessesUrl) {
setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/api/user/login"));
}
这个方法的代码我参考了父类的写法,将自己想要的url配置进去
天真的我以为这样就能解决问题了,可结果是,这个方法它根本就没有被调用!
问题解决
最后我发现,如果在JwtLoginFilter这个类中重写过滤器的doFilter方法,是会被调用的!那么我可以在doFilter中设置我想要的这个url,然后再继续执行过滤器链,这样Spring内部就会修改默认的认证处理器url,从而调用我自定义过滤器的attemptAutentication方法,走自己的认证逻辑,问题成功解决!
JwtLoginFilter完整代码如下:
@Slf4j
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {
private final RsaKeys rsaKeys;
private final AuthenticationManager authenticationManager;
public JwtLoginFilter(AuthenticationManager manager, RsaKeys rsaKeys) {
this.authenticationManager = manager;
this.rsaKeys = rsaKeys;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("进来自定义的过滤器了");
setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/api/user/login"));
super.doFilter(request, response, chain);
}
/**
* 获取用户的登录信息(账号,密码),并进行校验
* @param request 请求对象
* @param response 响应对象
* @return 认证管理器的认证结果
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
log.info("调用了自定义的登录认证过滤器");
// 获取登录参数
String account = request.getParameter("account");
String password = request.getParameter("password");
if(!StringUtils.hasText(account) || !StringUtils.hasText(password)) {
// 参数不合法
ResponseUtils.write(response, ResponseVO.fail(SysCode.UN_VALID_REQUEST_PARAMETER));
return null;
}
// 构造SpringSecurity登录token
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(account, password);
return this.authenticationManager.authenticate(token);
}
/**
* 用户认证成功,使用JWT生成Token返回
* @param request 请求对象
* @param response 响应对象
* @param chain 过滤器链
* @param authResult 认证结果
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
// 获取认证用户对象,去除敏感信息
User user = (User) authResult.getPrincipal();
user.setPassword(null);
// 生成Token
String authorization = JwtUtils.generateToken(user, rsaKeys.getPrivateKey());
// 存入响应头并返回
response.setHeader("Authorization", "Bearer " + authorization);
ResponseUtils.write(response, ResponseVO.ok(SysCode.LOGIN_SUCCESS));
}
/**
* 认证失败
* @param request 请求对象
* @param response 响应对象
* @param failed 失败原因
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
ResponseUtils.write(response, ResponseVO.fail(SysCode.BAD_AUTHENTICATE));
}
}
在SpringSecurity配置类中,无需添加http.formLogin配置,只要配置自定义过滤器即可
@Override
protected void configure(HttpSecurity http) throws Exception {
// 放行所有接口,由注解进行限制
http.authorizeRequests()
.antMatchers("/**").permitAll();
// 添加自定义的过滤器
http.addFilter(new JwtLoginFilter(authenticationManager(), rsaKeys))
.addFilter(new JwtVerifyTokenFilter(authenticationManager(), rsaKeys));
// csrf拦截
http.csrf().disable();
}
以上就是本篇文章的所有内容了,希望对你有帮助!
我的 个人博客 上线啦,欢迎到访~