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

网友投稿 267 2023-02-20


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

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.securhttp://ity.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

@EnableWebSecurity

public class SecurityConfig extends AbstractSecurityWebApplicationInitializer {

@Bean

public PasswordEncoder passwordEncoder() {

return new StandardPasswordEncoder("MD5");

}

@Autowired

private CustomerServichttp://e 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 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

${SPRING_SECURITY_LAST_EXCEPTION.message}

username:

password:

validateCode:

记住我

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

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


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:微信小程序实现文字跑马灯效果
下一篇:Java语言Consistent Hash算法学习笔记(代码示例)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~