spring security4 添加验证码

spring security是一个很大的模块,本文中只涉及到了自定义参数的认证。spring security默认的验证参数只有username和password,一般来说都是不够用的。由于时间过太久,有些忘,可能有少许遗漏。好了,不废话。
spring以及spring security配置采用javaConfig,版本依次为4.2.54.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
@EnableWebSecurity
public 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验证流程就走完了

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值