SpringBoot+MyBatis-plus+SpringSecurity+JWT 验证码认证
虾米姐
阅读:655
2021-03-31 12:47:17
评论:0
本文基于SpringBoot+MyBatis-plus+SpringSecurity+JWT 登入认证,实现前后端分离基础之上追加验证码登入认证功能。
1、springsecurity 验证码模式核心代码
创建验证码过滤器
在filter中增加VerificationCodeLoginFilter。这里过滤的是”/auth/code”的请求,用不同的请求地址来区分不同的登录认证方式。
package com.digipower.sercurity.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.digipower.common.entity.Result;
import com.digipower.sercurity.entity.JwtUserDetails;
import com.digipower.sercurity.token.VerificationCodeAuthenticationToken;
import com.digipower.sercurity.util.JwtTokenUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
public class VerificationCodeLoginFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
public static final String SPRING_SECURITY_FORM_CODE_KEY = "code";
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private String codeParameter = SPRING_SECURITY_FORM_CODE_KEY;
/**
* 是否仅 POST 方式
*/
private boolean postOnly = true;
/**
* 获取授权管理, 创建VerificationCodeLoginFilter时获取
*/
private AuthenticationManager authenticationManager;
public VerificationCodeLoginFilter(String defaultFilterProcessesUrl,AuthenticationManager authenticationManager) {
super(new AntPathRequestMatcher(defaultFilterProcessesUrl, "POST"));
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
// TODO Auto-generated method stub
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
String code = obtainCode(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
if (code == null) {
code = "";
}
username = username.trim();
code = code.trim();
VerificationCodeAuthenticationToken authRequest = new VerificationCodeAuthenticationToken(username, password,
code);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return authenticationManager.authenticate(authRequest);
}
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(passwordParameter);
}
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(usernameParameter);
}
protected String obtainCode(HttpServletRequest request) {
return request.getParameter(codeParameter);
}
protected void setDetails(HttpServletRequest request, VerificationCodeAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
/**
* TODO 一旦调用 springSecurity认证登录成功,立即执行该方法
*
* @param request
* @param response
* @param chain
* @param authResult
* @throws IOException
* @throws ServletException
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException {
// 生成jwt,并返回
VerificationCodeAuthenticationToken token = (VerificationCodeAuthenticationToken)authResult;
JwtUserDetails userEntity = new JwtUserDetails();
if(token.getPrincipal() != null){
userEntity.setUserName(String.valueOf(token.getPrincipal()));
}
if(token.getAuthorities() != null && token.getAuthorities().size() > 0){
userEntity.setAuthorities(token.getAuthorities());
}
if(token.getCredentials() != null){
userEntity.setPassword(String.valueOf(token.getCredentials()));
}
String jwtToken = JwtTokenUtil.generateToken(userEntity);
ObjectMapper objectMapper = new ObjectMapper();
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream out = response.getOutputStream();
String str = objectMapper.writeValueAsString(Result.ok().setDatas("token", jwtToken));
out.write(str.getBytes("UTF-8"));
out.flush();
out.close();
}
/**
* 认证失败,改方法被调用
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException ex) throws IOException, ServletException {
ObjectMapper objectMapper = new ObjectMapper();
try {
if (ex instanceof UsernameNotFoundException || ex instanceof BadCredentialsException) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(Result.error("401", "用户名或密码错误")));
} else if (ex instanceof InternalAuthenticationServiceException) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(Result.error("401", "没有账号信息")));
} else if (ex instanceof DisabledException) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(Result.error("401", "账户被禁用")));
} else {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(Result.error("401", "登录失败!")));
}
} catch (Exception e) {
}
}
}
创建验证码认证器
类文件说明: supports() 方法:支持自定义Token 类型(VerificationCodeAuthenticationToken)
authenticate()方法: 认证器核心方法
package com.digipower.sercurity.provider;
import java.io.Serializable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.digipower.entity.SysVerificationCode;
import com.digipower.sercurity.token.VerificationCodeAuthenticationToken;
import com.digipower.sercurity.userservice.UserDetailServiceImpl;
import com.digipower.service.SysVerificationCodeService;
@Component
public class VerificationCodeProvider implements AuthenticationProvider{
@Autowired
private UserDetailServiceImpl userDetailServiceImpl;
@Autowired
private SysVerificationCodeService sysVerificationCodeService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// TODO Auto-generated method stub
VerificationCodeAuthenticationToken token = (VerificationCodeAuthenticationToken) authentication;
String username = (String) token.getPrincipal();// 返回用户名;
String password = (String) token.getCredentials();// 返回密码
String code = token.getCode(); // 返回验证码
// 验证码校验
this.checkCode(code);
// 用户验证
UserDetails userDetails = userDetailServiceImpl.loadUserByUsername(username);
// 密码验证
this.checkPassword(userDetails, password);
// 此时鉴权成功后,应当重新 new 一个拥有鉴权的 authenticationResult 返回
VerificationCodeAuthenticationToken authenticationResult = new VerificationCodeAuthenticationToken(userDetails.getAuthorities(), username, password, code);
authenticationResult.setDetails(token.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
// TODO Auto-generated method stub
return (VerificationCodeAuthenticationToken.class.isAssignableFrom(authentication));
}
private void checkCode(String code){
QueryWrapper<SysVerificationCode> queryWrapper = new QueryWrapper<SysVerificationCode>();
queryWrapper.eq("code", code);
SysVerificationCode object = sysVerificationCodeService.getOne(queryWrapper);
if(object == null){
throw new BadCredentialsException("验证码错误");
}
// 验证码验证成功,数据库移除对应指定验证码记录
sysVerificationCodeService.remove(queryWrapper);
}
private void checkPassword(UserDetails userDetails, String password){
if(!String.valueOf(userDetails.getPassword()).equalsIgnoreCase(password)){
throw new BadCredentialsException("密码错误");
}
}
}
创建验证码凭证
package com.digipower.sercurity.token;
import java.util.Collection;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
@SuppressWarnings("serial")
public class VerificationCodeAuthenticationToken extends AbstractAuthenticationToken {
private final Object principal;
private Object credentials;
private String code;
public VerificationCodeAuthenticationToken(Object principal,
Object credentials, String code) {
super(null);
this.principal = principal;
this.credentials = credentials;
this.code = code;
setAuthenticated(false);
}
public VerificationCodeAuthenticationToken(Collection<? extends GrantedAuthority> authorities, Object principal,
Object credentials, String code) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
this.code = code;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
// TODO Auto-generated method stub
return this.credentials;
}
@Override
public Object getPrincipal() {
// TODO Auto-generated method stub
return this.principal;
}
public String getCode() {
return this.code;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
// TODO Auto-generated method stub
super.eraseCredentials();
credentials = null;
}
}
WebSecurityConfig配置类修改
package com.digipower.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import com.digipower.sercurity.filter.JWTLoginFilter;
import com.digipower.sercurity.filter.JWTValidFilter;
import com.digipower.sercurity.filter.VerificationCodeLoginFilter;
import com.digipower.sercurity.provider.VerificationCodeProvider;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("userDetailServiceImpl")
private UserDetailsService userDetailService;
/**
* 自定义Provider
*/
@Autowired
private VerificationCodeProvider verificationCodeProvider;
/**
* 认证
*
* @return
*/
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
//对默认的UserDetailsService进行覆盖
authenticationProvider.setUserDetailsService(userDetailService);
authenticationProvider.setPasswordEncoder(new PasswordEncoder() {
// 对密码未加密
@Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString();
}
// 判断密码是否正确, rawPassword 用户输入的密码, encodedPassword 数据库DB的密码,当 userDetailService的loadUserByUsername方法执行完后执行
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return rawPassword.toString().equalsIgnoreCase(encodedPassword);
}
});
return authenticationProvider;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO Auto-generated method stub
auth.authenticationProvider(authenticationProvider());
auth.authenticationProvider(verificationCodeProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilter(new JWTValidFilter(authenticationManager()));
http.addFilter(new JWTLoginFilter(authenticationManager())).csrf().disable();
VerificationCodeLoginFilter verificationCodeLoginFilter = new VerificationCodeLoginFilter("/auth/code",authenticationManager());
http.addFilterAfter(verificationCodeLoginFilter, UsernamePasswordAuthenticationFilter.class);
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 系统默认登入入口
.antMatchers("/auth/login").permitAll()
.antMatchers("/auth/code").permitAll()
// swagger 2不需要鉴权
.antMatchers("/swagger-ui.html/**").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/v2/api-docs/**").permitAll()
// 验证码不需要鉴权
.antMatchers("/defaultKaptcha").permitAll()
.anyRequest().authenticated(); // 任何请求,登录后可以访问
// 开启跨域访问
http.cors().disable();
// 开启模拟请求,比如API POST测试工具的测试,不开启时,API POST为报403错误
http.csrf().disable();
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource configurationSource = new UrlBasedCorsConfigurationSource();
CorsConfiguration cors = new CorsConfiguration();
cors.setAllowCredentials(true);
cors.addAllowedOrigin("*");
cors.addAllowedHeader("*");
cors.addAllowedMethod("*");
configurationSource.registerCorsConfiguration("/**", cors);
return new CorsFilter(configurationSource);
}
}
效果演示: 用户名+ 密码+ 验证码模式
效果演示: 用户名+ 密码模式
推导:基于本文可以完成基于springsecurity邮箱验证和springsecurity手机号码验证功能开发。
github 地址:https://github.com/zhouzhiwengang/baoan-house
声明
1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。