不用过滤器(使用json传参方式登录)
SecurityConfig配置
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* spring security配置
*/
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/** 认证失败 处理类 */
@Autowired
private AuthenticationEntryPointImpl unauthorizedHandler;
/** 退出 处理类 */
@Autowired
private LogoutSuccessHandlerImpl logoutSuccessHandler;
/** jwt token认证过滤器 */
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
/** 手机验证码 认证器 */
@Autowired
private CaptchaAuthenticationProvider captchaAuthenticationProvider;
/** 账号密码 认证器*/
@Autowired
private UserNameAuthenticationProvider userNameAuthenticationProvider;
/**
* 认证管理 AuthenticationManager
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.cors()
.and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
// 过滤请求
.authorizeRequests()
// 允许匿名访问
.antMatchers(antMatchersAnonymous()).anonymous()
.antMatchers(HttpMethod.GET, antMatchersPermitAll()).permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
// 退出处理
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter(必须配置与UsernamePasswordAuthenticationFilter之前)
httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
// 允许匿名访问url
private String[] antMatchersAnonymous() {
return new String[]{"/login" };
}
// 不需要任何限制url(静态资源)
private String[] antMatchersPermitAll() {
return new String[]{"/*.html"};
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 添加认证器
// 新增短信验证码验证
auth.authenticationProvider(captchaAuthenticationProvider);
// 账号密码登录验证
auth.authenticationProvider(userNameAuthenticationProvider);
}
/**
* 密码加密规则 强散列哈希加密实现(密码加密算法)
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
账号密码
UserNameAuthenticationProvider 账号密码认证器
import java.util.Collections;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
/**
* 用户名密码认证器
*/
@Slf4j
@Component
public class UserNameAuthenticationProvider implements AuthenticationProvider {
/** 进行账号认证实现 */
@Autowired
private UserDetailsService userDetailsService;
/** 密码加密规则 */
@Autowired
private PasswordEncoder bCryptPasswordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
long time = System.currentTimeMillis();
log.info("用户名/密码 开始登录验证 time:{}", time);
String username = authentication.getName();
String password = authentication.getCredentials().toString();
// 1、去调用自己实现的UserDetailsService,返回UserDetails
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 2、密码进行检查,这里调用了PasswordEncoder,检查 UserDetails 是否可用。
if (Objects.isNull(userDetails) || !bCryptPasswordEncoder.matches(password, userDetails.getPassword())) {
throw new BadCredentialsException("账号或密码错误");
}
// 3、返回经过认证的Authentication
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userDetails, null, Collections.emptyList());
result.setDetails(authentication.getDetails());
log.info("用户名/密码 登录验证完成 time:{}, existTime:{}", time, (System.currentTimeMillis() - time));
return result;
}
@Override
public boolean supports(Class<?> authentication) {
boolean res = UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
log.info("用户名/密码 是否进行登录验证 res:{}", res);
return res;
}
}
手机号验证码
CaptchaAuthenticationToken 短信验证码认证凭证
import java.util.Collection;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
/**
* 短信验证码认证凭证
*/
public class CaptchaAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal; // 手机号
private String captcha; // 验证码
/**
* 此构造函数用来初始化未授信凭据.
*
* @param principal
* @param captcha
*/
public CaptchaAuthenticationToken(Object principal, String captcha) {
super(null);
this.principal = principal;
this.captcha = captcha;
setAuthenticated(false);
}
/**
* 此构造函数用来初始化授信凭据.
*
* @param principal
* @param captcha
* @param authorities
*/
public CaptchaAuthenticationToken(Object principal, String captcha, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.captcha = captcha;
super.setAuthenticated(true);
}
public Object getCredentials() {
return this.captcha;
}
public Object getPrincipal() {
return this.principal;
}
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() {
super.eraseCredentials();
captcha = null;
}
}
CaptchaAuthenticationProvider 短信验证码认证器
import java.util.Collections;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
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.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
/**
* 短信验证码认证器
*/
@Slf4j
@Component
public class CaptchaAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserDetailsService userDetailsService;
/** 验证码验证服务 */
@Autowired
private ICaptchaService captchaService;
/**
* 进行验证码认证
*
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
long time = System.currentTimeMillis();
log.info("手机验证码 开始登录验证 time:{}", time);
String phone = authentication.getName();
String rawCode = authentication.getCredentials().toString();
// 1. 手机验证码验证
captchaService.validate("admin:account:login:key:", phone, rawCode);
// 1.根据手机号获取用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(phone);
if (Objects.isNull(userDetails)) {
throw new BadCredentialsException("当前手机号不存在");
}
// 3、返回经过认证的Authentication
CaptchaAuthenticationToken result = new CaptchaAuthenticationToken(userDetails, null, Collections.emptyList());
result.setDetails(authentication.getDetails());
log.info("手机验证码 登录验证完成 time:{}, existTime:{}", time, (System.currentTimeMillis() - time));
return result;
}
@Override
public boolean supports(Class<?> authentication) {
boolean res = CaptchaAuthenticationToken.class.isAssignableFrom(authentication);
log.info("手机验证码 是否进行登录验证 res:{}", res);
return res;
}
}
ICaptchaService
/**
* 验证码
*/
public interface ICaptchaService {
/**
* 缓存验证码
*
* @param key 缓存前缀key
* @param phone
* @param code
*/
public void setCaptcha(String key, String phone, String code);
/**
* 验证验证码
*
* @param key 缓存前缀key
* @param phone
* @param code
* CustomException 自定义异常
*/
public boolean validate(String key, String phone, String code) throws CustomException;
}
CaptchaServiceImpl 实现类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 验证码
*/
@Component
public class CaptchaServiceImpl implements ICaptchaService {
@Autowired
private RedisUtil redisUtil;
@Override
public void setCaptcha(String key, String phone, String code) {
// 有效期5分钟
redisUtil.set(key + phone, code, 300);
}
@Override
public boolean validate(String key, String phone, String code) throws CustomException {
if (StrUtils.isBlank(phone) || StrUtils.isBlank(code)) {
throw new CustomException("验证验证码参数为空", 300);
}
// 获取缓存数据
Object v = redisUtil.get(key + phone);
if (null == v) {
throw new CustomException("验证码过期,请重新获取", 300);
}
// 验证是否正确
if (!code.equalsIgnoreCase(String.valueOf(v))) {
throw new CustomException("验证码错误", 300);
}
// 验证通过 清除缓存
redisUtil.delete(key + phone);
return true;
}
}
使用
下面为简版登录 具体写法以公司为准
@Slf4j
@RestController
public class LoginController {
/**
* 用户名密码 登录
* LoginBody 实体字段 (username)用户名, (password)用户密码/验证码, (type)登录类型 1-密码登录, 2-短信验证码登录 默认1
*/
@PostMapping("/login")
public Boolean login(@RequestBody LoginBody loginBody) {
loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getType());
return true;
}
}
LoginService
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
/**
* 登录校验方法
*
*/
@Slf4j
@Component
public class SysLoginService {
@Resource
private AuthenticationManager authenticationManager;
/**
* 登录验证
*
* @param username 用户名
* @param password 密码
* @param type 登录类型 1-密码登录, 2-短信验证码登录 默认1
* @return 结果
*/
public String login(String username, String password, Integer type) {
// 用户验证
Authentication authentication = null;
try {
if (null == type || type == 1) {
// 密码登录
authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} else {
// 短信验证码登录
authentication = authenticationManager.authenticate(new CaptchaAuthenticationToken(username, password));
}
} catch (Exception e) {
log.error("登录验证异常", e);
// 登录异常 自己处理业务逻辑
}
return null;
}
}
使用过滤器实现(使用form表单传参方式登录)
推荐看这篇文章 https://www.felord.cn/captchaAuthenticationFilter.html
主要是自己实现过滤器 过滤器继承AbstractAuthenticationProcessingFilter