spring security 短信验证码
*************************
示例
***************
config 层
MessageCodeAuthenticationToken:自定义认证token
public class MessageCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 500L;
private final Object principal;
public MessageCodeAuthenticationToken(Object principal) {
super(null);
this.principal = principal;
this.setAuthenticated(false);
}
public MessageCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
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");
} else {
super.setAuthenticated(false);
}
}
public void eraseCredentials() {
super.eraseCredentials();
}
}
MessageCodeAuthenticationProvider:检验短信验证码,构造token
@Component
public class MessageCodeAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
MessageCodeAuthenticationToken authenticationToken=(MessageCodeAuthenticationToken)authentication;
String phone=authenticationToken.getPrincipal().toString();
System.out.println(phone);
checkMessageCode(phone);
System.out.println("认证成功");
MessageCodeAuthenticationToken authenticationResult=new MessageCodeAuthenticationToken(phone,
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
private void checkMessageCode(String phone){
HttpServletRequest request=((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
String request_code=request.getParameter("code");
Map<String,String> map=(Map<String, String>) request.getSession().getAttribute("message_code");
if (map==null){
throw new BadCredentialsException("未申请验证码");
}
String session_phone= map.get("phone");
String session_code= map.get("code");
if (!phone.equals(session_phone)){
throw new BadCredentialsException("申请验证的手机号不一致");
}
if (!request_code.equals(session_code)){
throw new BadCredentialsException("验证码不一致");
}
}
@Override
public boolean supports(Class<?> aClass) {
return MessageCodeAuthenticationToken.class.isAssignableFrom(aClass);
}
}
MessageCodeAuthenticationFilter:短信验证码拦截器,拦截路径:/message/login
public class MessageCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private String MessageCodeParameter="phone";
private boolean postOnly = true;
public MessageCodeAuthenticationFilter(){
super(new AntPathRequestMatcher("/message/login", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
if (this.postOnly && !httpServletRequest.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + httpServletRequest.getMethod());
}else {
String phone=httpServletRequest.getParameter(MessageCodeParameter);
if (phone==null){
phone="";
}
phone=phone.trim();
MessageCodeAuthenticationToken authRequest=new MessageCodeAuthenticationToken(phone);
this.setDetails(httpServletRequest,authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
protected void setDetails(HttpServletRequest request, MessageCodeAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
public void setMessageCodeParameter(String messageCodeParameter){
Assert.hasText(messageCodeParameter, "MessageCode parameter must not be empty or null");
this.MessageCodeParameter=messageCodeParameter;
}
public String getMessageCodeParameter() {
return MessageCodeParameter;
}
}
MessageCodeAuthenticationFailureHandler:短信认证失败相关处理
@Component
public class MessageCodeAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(e.getMessage());
}
}
WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private MessageCodeFilter messageCodeFilter;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(messageCodeFilter).addPathPatterns("/message/code");
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login/form").setViewName("login");
}
}
MessageCodeSecurityConfig:短信安全相关配置
@Configuration
public class MessageCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Resource
private MessageCodeAuthenticationProvider messageCodeAuthenticationProvider;
@Resource
private MessageCodeAuthenticationFailureHandler messageCodeAuthenticationFailureHandler;
@Override
public void configure(HttpSecurity builder) throws Exception {
MessageCodeAuthenticationFilter messageCodeAuthenticationFilter=new MessageCodeAuthenticationFilter();
messageCodeAuthenticationFilter.setAuthenticationManager(builder.getSharedObject(AuthenticationManager.class));
messageCodeAuthenticationFilter.setAuthenticationFailureHandler(messageCodeAuthenticationFailureHandler);
builder.authenticationProvider(messageCodeAuthenticationProvider)
.addFilterAfter(messageCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
WebSecurityConfig
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private MessageCodeSecurityConfig messageCodeSecurityConfig;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginPage("/login/form").and().authorizeRequests()
.antMatchers("/hello").hasAuthority("ROLE_USER")
.antMatchers("/**").permitAll();
http.apply(messageCodeSecurityConfig);
}
}
***************
filter 层
MessageCodeFilter:拦截路径 /message/code,检验手机号是否存在
@Component
public class MessageCodeFilter implements HandlerInterceptor {
private List<String> messageList= Arrays.asList("123456","654321");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String message=request.getParameter("phone");
if (!messageList.contains(message)){
response.getWriter().write("手机号不存在");
return false;
}else {
return true;
}
}
}
***************
controller 层
HelloController
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(Principal principal){
return "hello "+principal.getName();
}
@RequestMapping("/message/code")
public Map<String,String> messageCode(HttpServletRequest request){
String phone=request.getParameter("phone");
String code=generateCode();
System.out.println("code:"+code);
Map<String,String> map=new HashMap<>();
map.put("phone",phone);
map.put("code",code);
request.getSession().setAttribute("message_code",map);
return map;
}
private String generateCode(){
StringBuilder buffer=new StringBuilder();
Random r=new Random();
for (int i=0;i<6;i++){
buffer.append(r.nextInt(10));
}
return buffer.toString();
}
}
***************
前端页面
login.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="/js/jquery-3.5.1.min.js"></script>
<script>
$(function(){
$("#get_code").click(function () {
$.get({
url: "/message/code?phone="+$("#phone").val(),
success: function (result) {
$("#result_code").html(result.code)
}
})
})
})
</script>
</head>
<body>
<form th:action="@{/message/login}" th:align="center" method="post">
phone:<input type="text" id="phone" name="phone"><br>
code:<input type="text" id="code" name="code">
<a th:id="get_code">获取短信验证码</a><br>
<button th:id="code_login">code</button>
</form>
<div th:id="result_code" align="center"></div>
</body>
</html>
*************************
使用测试
localhost:8080/hello
认证通过后,输出:hello 123456