首页 > 编程 > Java > 正文

spring security4 添加验证码的示例代码

2019-11-26 10:20:35
字体:
来源:转载
供稿:网友

spring security是一个很大的模块,本文中只涉及到了自定义参数的认证。spring security默认的验证参数只有username和password,一般来说都是不够用的。由于时间过太久,有些忘,可能有少许遗漏。好了,不废话。
spring以及spring security配置采用javaConfig,版本依次为4.2.5,4.0.4
总体思路:自定义EntryPoint,添加自定义参数扩展AuthenticationToken以及AuthenticationProvider进行验证。

首先定义EntryPoint:

import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class MyAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {  public MyAuthenticationEntryPoint(String loginFormUrl) {    super(loginFormUrl);  }  @Override  public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {    super.commence(request, response, authException);  }}

接下来是token,validCode是验证码参数:

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;public class MyUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {  private String validCode;  public MyUsernamePasswordAuthenticationToken(String principal, String credentials, String validCode) {    super(principal, credentials);    this.validCode = validCode;  }  public String getValidCode() {    return validCode;  }  public void setValidCode(String validCode) {    this.validCode = validCode;  }}

继续ProcessingFilter,

import com.core.shared.ValidateCodeHandle;import org.springframework.security.core.Authentication;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import java.io.IOException;public class MyValidCodeProcessingFilter extends AbstractAuthenticationProcessingFilter {  private String usernameParam = "username";  private String passwordParam = "password";  private String validCodeParam = "validateCode";  public MyValidCodeProcessingFilter() {    super(new AntPathRequestMatcher("/user/login", "POST"));  }  @Override  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {    String username = request.getParameter(usernameParam);    String password = request.getParameter(passwordParam);    String validCode = request.getParameter(validCodeParam);    valid(validCode, request.getSession());    MyUsernamePasswordAuthenticationToken token = new MyUsernamePasswordAuthenticationToken(username, password, validCode);    return this.getAuthenticationManager().authenticate(token);  }  public void valid(String validCode, HttpSession session) {    if (validCode == null) {      throw new ValidCodeErrorException("验证码为空!");    }    if (!ValidateCodeHandle.matchCode(session.getId(), validCode)) {      throw new ValidCodeErrorException("验证码错误!");    }  }}

分别定义三个参数,用于接收login表单过来的参数,构造方法给出了login的url以及需要post方式

接下来就是认证了,此处还没到认证用户名和密码的时候,只是认证了验证码

下面是ValidateCodeHandle一个工具类以及ValidCodeErrorException:

import java.util.concurrent.ConcurrentHashMap;public class ValidateCodeHandle {  private static ConcurrentHashMap validateCode = new ConcurrentHashMap<>();  public static ConcurrentHashMap getCode() {    return validateCode;  }  public static void save(String sessionId, String code) {    validateCode.put(sessionId, code);  }  public static String getValidateCode(String sessionId) {    Object obj = validateCode.get(sessionId);    if (obj != null) {      return String.valueOf(obj);    }    return null;  }  public static boolean matchCode(String sessionId, String inputCode) {    String saveCode = getValidateCode(sessionId);    if (saveCode.equals(inputCode)) {      return true;    }    return false;  }}

这里需要继承AuthenticationException以表明它是security的认证失败,这样才会走后续的失败流程

import org.springframework.security.core.AuthenticationException;public class ValidCodeErrorException extends AuthenticationException {  public ValidCodeErrorException(String msg) {    super(msg);  }  public ValidCodeErrorException(String msg, Throwable t) {    super(msg, t);  }}

接下来是Provider:

import org.springframework.security.authentication.BadCredentialsException;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.authentication.dao.DaoAuthenticationProvider;import org.springframework.security.core.AuthenticationException;import org.springframework.security.core.userdetails.UserDetails;public class MyAuthenticationProvider extends DaoAuthenticationProvider {  @Override  public boolean supports(Class<?> authentication) {    return MyUsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);  }  @Override  protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {    Object salt = null;    if (getSaltSource() != null) {      salt = getSaltSource().getSalt(userDetails);    }    if (authentication.getCredentials() == null) {      logger.debug("Authentication failed: no credentials provided");      throw new BadCredentialsException("用户名或密码错误!");    }    String presentedPassword = authentication.getCredentials().toString();    if (!this.getPasswordEncoder().isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {      logger.debug("Authentication failed: password does not match stored value");      throw new BadCredentialsException("用户名或密码错误!");    }  }}

其中supports方法指定使用自定义的token,additionalAuthenticationChecks方法和父类的逻辑一模一样,我只是更改了异常返回的信息。

接下来是处理认证成功和认证失败的handler

import org.springframework.security.core.Authentication;import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class FrontAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {  public FrontAuthenticationSuccessHandler(String defaultTargetUrl) {    super(defaultTargetUrl);  }  @Override  public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {    super.onAuthenticationSuccess(request, response, authentication);  }}
import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class FrontAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {  public FrontAuthenticationFailureHandler(String defaultFailureUrl) {    super(defaultFailureUrl);  }  @Override  public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {    super.onAuthenticationFailure(request, response, exception);  }}

最后就是最重要的security config 了:

import com.service.user.CustomerService;import com.web.filter.SiteMeshFilter;import com.web.mySecurity.*;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.annotation.Order;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.authentication.AuthenticationProvider;import org.springframework.security.authentication.ProviderManager;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.crypto.password.PasswordEncoder;import org.springframework.security.crypto.password.StandardPasswordEncoder;import org.springframework.security.web.access.ExceptionTranslationFilter;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;import org.springframework.web.filter.CharacterEncodingFilter;import javax.servlet.DispatcherType;import javax.servlet.FilterRegistration;import javax.servlet.ServletContext;import java.util.ArrayList;import java.util.EnumSet;import java.util.List;@Configuration@EnableWebSecuritypublic class SecurityConfig extends AbstractSecurityWebApplicationInitializer {  @Bean  public PasswordEncoder passwordEncoder() {    return new StandardPasswordEncoder("MD5");  }  @Autowired  private CustomerService customerService;  @Configuration  @Order(1)  public static class FrontendWebSecurityConfigureAdapter extends WebSecurityConfigurerAdapter {    @Autowired    private MyValidCodeProcessingFilter myValidCodeProcessingFilter;    @Override    protected void configure(HttpSecurity http) throws Exception {      http.csrf().disable()           .authorizeRequests()          .antMatchers("/user/login", "/user/logout").permitAll()          .anyRequest().authenticated()          .and()          .addFilterBefore(myValidCodeProcessingFilter, UsernamePasswordAuthenticationFilter.class)          .formLogin()          .loginPage("/user/login")          .and()          .logout()          .logoutUrl("/user/logout")          .logoutSuccessUrl("/user/login");    }  }  @Bean(name = "frontAuthenticationProvider")  public MyAuthenticationProvider frontAuthenticationProvider() {    MyAuthenticationProvider myAuthenticationProvider = new MyAuthenticationProvider();    myAuthenticationProvider.setUserDetailsService(customerService);    myAuthenticationProvider.setPasswordEncoder(passwordEncoder());    return myAuthenticationProvider;  }  @Bean  public AuthenticationManager authenticationManager() {    List<AuthenticationProvider> list = new ArrayList<>();    list.add(frontAuthenticationProvider());    AuthenticationManager authenticationManager = new ProviderManager(list);    return authenticationManager;  }  @Bean  public MyValidCodeProcessingFilter myValidCodeProcessingFilter(AuthenticationManager authenticationManager) {    MyValidCodeProcessingFilter filter = new MyValidCodeProcessingFilter();    filter.setAuthenticationManager(authenticationManager);    filter.setAuthenticationSuccessHandler(frontAuthenticationSuccessHandler());    filter.setAuthenticationFailureHandler(frontAuthenticationFailureHandler());    return filter;  }  @Bean  public FrontAuthenticationFailureHandler frontAuthenticationFailureHandler() {    return new FrontAuthenticationFailureHandler("/user/login");  }  @Bean  public FrontAuthenticationSuccessHandler frontAuthenticationSuccessHandler() {    return new FrontAuthenticationSuccessHandler("/front/test");  }  @Bean  public MyAuthenticationEntryPoint myAuthenticationEntryPoint() {    return new MyAuthenticationEntryPoint("/user/login");  }}

首先是一个加密类的bean,customerService是一个简单的查询用户

@Service("customerService")public class CustomerServiceImpl implements CustomerService {  @Autowired  private UserDao userDao;  @Override  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {    return userDao.findCustomerByUsername(username);  }}

 下来就是FrontendWebSecurityConfigureAdapter了,重写了configure方法,先禁用csrf, 开启授权请求authorizeRequests(),其中”/user/login”, “/user/logout”放过权限验证, 其他请求需要进行登录认证, 然后是addFilterBefore(),把我自定义的myValidCodeProcessingFilter添加到security默认的UsernamePasswordAuthenticationFilter之前,也就是先进行我的自定义参数认证, 然后是formLogin(),配置登录url以及登出url,登录登出url都需要进行controller映射,也就是要自己写controller。
接下来就是AuthenticationProvider,AuthenticationManager,ProcessingFilter,AuthenticationFailureHandler,AuthenticationSuccessHandler,EntryPoint的bean显示声明。

下面是login.jsp

<body><div class="login_div">  <form:form autocomplete="false" commandName="userDTO" method="post">    <div>      <span class="error_tips"><b>${SPRING_SECURITY_LAST_EXCEPTION.message}</b></span>    </div>    username:<form:input path="username" cssClass="form-control"/><br/>    password:<form:password path="password" cssClass="form-control"/><br/>    validateCode:<form:input path="validateCode" cssClass="form-control"/>    <label>${validate_code}</label>    <div class="checkbox">      <label>        <input type="checkbox" name="remember-me"/>记住我      </label>    </div>    <input type="submit" class="btn btn-primary" value="submit"/>  </form:form></div></body>

 验证码验证失败的时候抛出的是ValidCodeErrorException,由于它继承AuthenticationException,security在验证的时候遇到AuthenticationException就会触发AuthenticationFailureHandler,上面的bean中声明了认证失败跳转到登录url,所以login.jsp里面有${SPRING_SECURITY_LAST_EXCEPTION.message}获取我认证时抛出异常信息,能友好的提示用户。

整个自定义security验证流程就走完了

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持武林网。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表