美文网首页SpringBoot极简教程 · Spring Boot Spring-Boot
Spring Security项目基于表单登陆(三)

Spring Security项目基于表单登陆(三)

作者: 郭少华 | 来源:发表于2019-05-27 19:32 被阅读16次

SpringSecurity基本原理

application.yml注释掉关闭security的配置

#先关闭security默认配置
#security:
#  basic:
#    enabled: false

创建配置文件

image.png

表单登陆方式

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin() //表单登陆页
        //http.httpBasic()   //security默认方式
            .and()
            .authorizeRequests()//下面授权配置
            .anyRequest()//所有请求
            .authenticated();//都需要身份认证
    }
}
image.png

账号是user密码是程序启动时生成的密码

默认登陆方式

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //http.formLogin() //表单登陆页
        http.httpBasic()   //security默认方式
            .and()
            .authorizeRequests()//下面授权配置
            .anyRequest()//所有请求
            .authenticated();//都需要身份认证
    }
}
image.png

过滤器链

自定义认证用户逻辑

打开密码加密


image.png
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    //密码加密
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin() //表单登陆页
        //http.httpBasic()   //security默认方式
            .and()
            .authorizeRequests()//下面授权配置
            .anyRequest()//所有请求
            .authenticated();//都需要身份认证
    }
}

@Component
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    //认证登陆
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //isAccountNonExpired 账号是否过期
        //isCredentialsNonExpired 密码是否过期
        //isAccountNonLocked 账号是否锁定用于冻结状态
        //isEnabled 账号是否可用用于删除状态

        //第一个参数是传进来的账号,第二个参数是数据库根据账号查询到到密码
        //第三个参数是账号是否可用,第四个参数是账号是否过期
        //第五个参数是密码是否过期,第六个参数账号是否锁定
        //第七个参数账号到权限
        return new User(username,userRepository.findByUsername(username).getPassword(),
                true,true,true,userRepository.findByUsername(username).getIsAccountNonLocked(),
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
    //模拟注册
    public void register(){
        com.guosh.security.browser.domain.User user=new com.guosh.security.browser.domain.User();
        user.setUsername("ceshi");
        //存入加密后的密码
        user.setPassword(passwordEncoder.encode("123456"));
        user.setIsEnabled(true);
        user.setIsAccountNonLocked(true);
        userRepository.save(user);
    }

}

个性化用户认证流程

自定义登陆页面并实现form表单提交

创建登陆页面

image.png
<!DOCTYPE html>
<html>  
<head>
<title>Login</title>
</head>
<body>
 <h1>Login Form</h1>
    <form action="/guoshsecurity/authentication/form" method="post">
        <input type="text" class="text" value="" name="username">
        <input type="password" value="" name="password">
        <input type="submit" value="Login" >
    </form>
</body>
</html>

添加登陆页面地址,并把登陆页面地址设置不需要认证,自定义form表单提交地址

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    //密码加密
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()//关闭跨站防护
                .authorizeRequests()//下面授权配置
                    .antMatchers("/defaultLogin.html").permitAll()//login请求除外不需要认证
                    .anyRequest().authenticated()//所有请求都需要身份认证
                .and()
                    .formLogin() //表单登陆页
                    .loginPage("/defaultLogin.html")//登陆页面
                    .loginProcessingUrl("/authentication/form");//自定义form表单登陆提交地址默认是/login



    }
}

处理不同类型的请求

image.png
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private SecurityProperties securityProperties;

    //密码加密
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()//关闭跨站防护
                .authorizeRequests()//下面授权配置
                    .antMatchers("/login",securityProperties.getBrowser().getLoginPage()).permitAll()//login请求除外不需要认证
                    .anyRequest().authenticated()//所有请求都需要身份认证
                .and()
                    .formLogin() //表单登陆页
                    .loginPage("/login")//登陆页面
                    .loginProcessingUrl("/authentication/form");//自定义form表单登陆提交地址默认是/login



    }
}

处理登陆请求是跳转到默认登陆页面或者用户自定义登陆页面

@RestController
public class BrowserSecurityController {

    private RequestCache requestCache=new HttpSessionRequestCache();

    private RedirectStrategy redirectStrategy=new DefaultRedirectStrategy();

    @Autowired
    private SecurityProperties securityProperties;
    /**
     * 需要身份认证时跳转地址
     * @param request
     * @param response
     * @return
     */
    @RequestMapping("/login")
    @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
    public SimpleResponse requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
        SavedRequest savedRequest=requestCache.getRequest(request,response);
        if(savedRequest!=null){
            String targetUrl = savedRequest.getRedirectUrl();
            //System.out.println(savedRequest);
            if(StringUtils.endsWithIgnoreCase(targetUrl,".html")){
                redirectStrategy.sendRedirect(request,response,securityProperties.getBrowser().getLoginPage());
            }
        }
        return new SimpleResponse("访问服务需要身份认证,请引导用户到登陆页");
    }
}

自定义登陆页面

image.png
@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityCoreConfig {
}

public class BrowserProperties {

    private String loginPage="/defaultLogin.html"; //如果用户没有配置登陆页面走默认

    public String getLoginPage() {
        return loginPage;
    }

    public void setLoginPage(String loginPage) {
        this.loginPage = loginPage;
    }
}


@ConfigurationProperties(prefix = "guosh.security")
public class SecurityProperties {

    private BrowserProperties browser=new BrowserProperties();

    public BrowserProperties getBrowser() {
        return browser;
    }

    public void setBrowser(BrowserProperties browser) {
        this.browser = browser;
    }
}

用户如果在application.yml指定了登陆页面会跳转指定登陆页

#自定义登陆页面
guosh:
  security:
    browser:
      loginPage: /demoLogin.html

自定义登陆成功处理

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private BrowserAuthenticationSuccessHandler browserAuthenticationSuccessHandler;

    @Autowired
    private BrowserAuthenticationFailureHandler browserAuthenticationFailureHandler;

    //密码加密
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()//关闭跨站防护
                .authorizeRequests()//下面授权配置
                    .antMatchers("/login",securityProperties.getBrowser().getLoginPage()).permitAll()//login请求除外不需要认证
                    .anyRequest().authenticated()//所有请求都需要身份认证
                .and()
                    .formLogin() //表单登陆页
                    .loginPage("/login")//登陆页面
                    .loginProcessingUrl("/authentication/form")//自定义form表单登陆提交地址默认是/login
                    .successHandler(browserAuthenticationSuccessHandler)//自定义登陆成功后返回json信息
                    .failureHandler(browserAuthenticationFailureHandler);//自定义登陆失败返回json

    }
}

创建自定义成功失败的实现方法


image.png
@Component
public class BrowserAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private Logger logger= LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;

    //自定义登陆成功
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        logger.info("登陆成功");

        response.setContentType("application/json;charset=UTF-8");
        //转json字符串返回
        response.getWriter().write(objectMapper.writeValueAsString(authentication));

    }
}
@Component
public class BrowserAuthenticationFailureHandler implements AuthenticationFailureHandler {
    private Logger logger= LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;

    //自定义登陆失败
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        logger.info("登陆失败");
        //401状态码
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json;charset=UTF-8");
        //转json字符串返回
        response.getWriter().write(objectMapper.writeValueAsString(e));
    }
}

自定义配置支持同步请求与异步请求

创建可配置的请求方式


image.png
public enum LoginType {
    REDITECT,
    JSON
}
public class BrowserProperties {

    //自定义登陆页面
    private String loginPage="/defaultLogin.html"; //如果用户没有配置登陆页面走默认

    //自定义处理登陆请求默认异步
    private LoginType loginType=LoginType.JSON;

    public String getLoginPage() {
        return loginPage;
    }

    public void setLoginPage(String loginPage) {
        this.loginPage = loginPage;
    }

    public LoginType getLoginType() {
        return loginType;
    }

    public void setLoginType(LoginType loginType) {
        this.loginType = loginType;
    }
}

创建自定义成功失败的实现方法


image.png

@Component
public class BrowserAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    private Logger logger= LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private SecurityProperties securityProperties;

    //自定义登陆成功
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        logger.info("登陆成功");
        //判断异步请求还是同步请求
        if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
            response.setContentType("application/json;charset=UTF-8");
            //转json字符串返回
            response.getWriter().write(objectMapper.writeValueAsString(authentication));
        }
        else {
            super.onAuthenticationSuccess(request,response,authentication);
        }


    }
}
@Component
public class BrowserAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    private Logger logger= LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private SecurityProperties securityProperties;

    //自定义登陆失败
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        logger.info("登陆失败");
        if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
            //401状态码
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            response.setContentType("application/json;charset=UTF-8");
            //转json字符串返回
            response.getWriter().write(objectMapper.writeValueAsString(e));
        }else{
            super.onAuthenticationFailure(request,response,e);
        }

    }
}

用户如果在application.yml指定了请求方式会按照指定的方式执行

guosh:
  security:
    browser:
      loginPage: /demoLogin.html
      #JSON异步登陆 REDITECT同步登陆
      loginType: JSON

获取登陆用户信息

    @RequestMapping(value = "/me",method = RequestMethod.GET)
    public Object getLoginUser() {
        //获取详细登陆信息
        //SecurityContextHolder.getContext().getAuthentication();
        //获取用户的登陆信息
        return ((UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal());

    }

图片验证码

图形验证码接口

image.png

验证码实体类

public class ImageCode {

    //验证码图片
    private BufferedImage image;
    //随机数
    private String code;
    //失效时间
    private LocalDateTime exireTime;

    public ImageCode(BufferedImage image, String code, int exireIn) {
        this.image = image;
        this.code = code;
        //当前时间加上过期秒数
        this.exireTime = LocalDateTime.now().plusSeconds(exireIn);
    }
    //判断时间是否过期
    public boolean isExpried(){
        return LocalDateTime.now().isAfter(exireTime);
    }

    public BufferedImage getImage() {
        return image;
    }

    public void setImage(BufferedImage image) {
        this.image = image;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public LocalDateTime getExireTime() {
        return exireTime;
    }

    public void setExireTime(LocalDateTime exireTime) {
        this.exireTime = exireTime;
    }
}

验证码接口实现

@RestController
public class ValidateCodeController {

    public static final String SESSION_KEY="SESSION_KEY_IMAGE_CODE";

    private SessionStrategy sessionStrategy=new HttpSessionSessionStrategy();

    @RequestMapping(value = "/code/image",method = RequestMethod.GET)
    public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //生成验证码对象
        ImageCode imageCode=caeateImageCode(request);
        //将ImageCode对象保存在session中
        sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode);
        //返回到页面
        ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream());
    }

    private ImageCode caeateImageCode(HttpServletRequest request) {
        int width = 67;
        int height = 23;
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

        Graphics g = image.getGraphics();

        Random random = new Random();

        g.setColor(getRandColor(200, 250));
        g.fillRect(0, 0, width, height);
        g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
        g.setColor(getRandColor(160, 200));
        for (int i = 0; i < 155; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int xl = random.nextInt(12);
            int yl = random.nextInt(12);
            g.drawLine(x, y, x + xl, y + yl);
        }

        String sRand = "";
        for (int i = 0; i < 4; i++) {
            String rand = String.valueOf(random.nextInt(10));
            sRand += rand;
            g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
            g.drawString(rand, 13 * i + 6, 16);
        }

        g.dispose();

        return new ImageCode(image, sRand, 60);
    }

    /**
     * 生成随机背景条纹
     *
     * @param fc
     * @param bc
     * @return
     */
    private Color getRandColor(int fc, int bc) {
        Random random = new Random();
        if (fc > 255) {
            fc = 255;
        }
        if (bc > 255) {
            bc = 255;
        }
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }


}

验证码的异常处理器

public class ValidateCodeException extends AuthenticationException {


    public ValidateCodeException(String msg) {
        super(msg);
    }
}

在登陆之前验证验证码的拦截

public class ValidateCodeFilter extends OncePerRequestFilter {

    //登陆失败的处理器
    private AuthenticationFailureHandler authenticationFailureHandler;

    private SessionStrategy sessionStrategy=new HttpSessionSessionStrategy();

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //必须是登陆请求并且是post请求
        if(StringUtils.equals("/guoshsecurity/authentication/form",request.getRequestURI())&&StringUtils.equalsIgnoreCase(request.getMethod(),"post")){
            try {
                //校验验证码
                validate(new ServletWebRequest(request));
            }catch (ValidateCodeException e){
                authenticationFailureHandler.onAuthenticationFailure(request,response,e);
                return;
            }
        }
        filterChain.doFilter(request,response);
    }

    private void validate(ServletWebRequest request) throws ServletRequestBindingException {
        //从session获取验证码
        ImageCode codeInSession=(ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY);
        //从请求里获取验证码
        String codeInRequest=ServletRequestUtils.getStringParameter(request.getRequest(),"imageCode");

        if(StringUtils.isBlank(codeInRequest)){
            throw new ValidateCodeException("验证码不能为空");
        }
        if(codeInSession == null){
            throw new ValidateCodeException("验证码不存在");
        }
        //判断验证码时间是否过期
        if(codeInSession.isExpried()){
            sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
            throw new ValidateCodeException("验证码已经过期");
        }
        //比对验证码
        if(!StringUtils.equalsIgnoreCase(codeInSession.getCode(),codeInRequest)){
            throw new ValidateCodeException("验证码不匹配");
        }
        sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
    }

    public AuthenticationFailureHandler getAuthenticationFailureHandler() {
        return authenticationFailureHandler;
    }

    public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
        this.authenticationFailureHandler = authenticationFailureHandler;
    }
}

修改WebSecurityConfig配置文件

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private BrowserAuthenticationSuccessHandler browserAuthenticationSuccessHandler;

    @Autowired
    private BrowserAuthenticationFailureHandler browserAuthenticationFailureHandler;


    @Autowired
    private SessionRegistry sessionRegistry;

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    //密码加密
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ValidateCodeFilter validateCodeFilter=new ValidateCodeFilter();
        validateCodeFilter.setAuthenticationFailureHandler(browserAuthenticationFailureHandler);


        http
                .csrf().disable()//关闭跨站防护
                .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)//在登陆拦截之前添加验证码拦截器
                .authorizeRequests()//下面授权配置
                    .antMatchers("/login",securityProperties.getBrowser().getLoginPage(),"/code/image").permitAll()//login请求除外不需要认证
                    .anyRequest().authenticated()//所有请求都需要身份认证
                .and()
                    .formLogin() //表单登陆页
                    .loginPage("/login")//登陆页面
                    .loginProcessingUrl("/authentication/form")//自定义form表单登陆提交地址默认是/login
                    .successHandler(browserAuthenticationSuccessHandler)//自定义登陆成功后返回json信息
                    .failureHandler(browserAuthenticationFailureHandler)//自定义登陆失败返回json
                .and()
                    .sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry).expiredUrl("/login");//用户只能登陆一次

    }
}

图形验证码重构

验证码基本参数,验证码拦截接口可配置

image.png
public class ImageCodeProperties {
    //默认验证码宽度
    private int width = 67;
    //默认验证码高度
    private int height = 23;
    //默认验证码位数
    private int length = 4;
    //默认验证码失效时间
    private int expreIn= 60;
    //哪些url地址需要验证码拦截
    private String url;
    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    public int getExpreIn() {
        return expreIn;
    }

    public void setExpreIn(int expreIn) {
        this.expreIn = expreIn;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}

public class ValidateCodeProperties {

    private ImageCodeProperties image=new ImageCodeProperties();

    public ImageCodeProperties getImage() {
        return image;
    }

    public void setImage(ImageCodeProperties image) {
        this.image = image;
    }
}
@ConfigurationProperties(prefix = "guosh.security")
public class SecurityProperties {
    //浏览器登陆配置
    private BrowserProperties browser=new BrowserProperties();
    //验证码配置
    private ValidateCodeProperties code=new ValidateCodeProperties();

    public BrowserProperties getBrowser() {
        return browser;
    }

    public void setBrowser(BrowserProperties browser) {
        this.browser = browser;
    }

    public ValidateCodeProperties getCode() {
        return code;
    }

    public void setCode(ValidateCodeProperties code) {
        this.code = code;
    }
}
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {

    //登陆失败的处理器
    private AuthenticationFailureHandler authenticationFailureHandler;

    private SessionStrategy sessionStrategy=new HttpSessionSessionStrategy();

    private Set<String>urls = new HashSet<>();

    private SecurityProperties securityProperties;

    private AntPathMatcher antPathMatcher=new AntPathMatcher();

    @Override
    public void afterPropertiesSet() throws ServletException {
        super.afterPropertiesSet();
        //分割转换数组
        String[]configUrls=StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getCode().getImage().getUrl(),",");
        for (String configUrl:configUrls) {
            urls.add(configUrl);
        }
        urls.add("/authentication/form");
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        boolean action = false;
        //用antPathMatcher工具栏判断路径是否符合
        for (String url:urls) {
            if(antPathMatcher.match(url,request.getServletPath())){
                action=true;
            }
        }

        //必须是登陆请求并且是post请求
        if(action){
            try {
                //校验验证码
                validate(new ServletWebRequest(request));
            }catch (ValidateCodeException e){
                authenticationFailureHandler.onAuthenticationFailure(request,response,e);
                return;
            }
        }
        filterChain.doFilter(request,response);
    }

    private void validate(ServletWebRequest request) throws ServletRequestBindingException {
        //从session获取验证码
        ImageCode codeInSession=(ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY);
        //从请求里获取验证码
        String codeInRequest=ServletRequestUtils.getStringParameter(request.getRequest(),"imageCode");

        if(StringUtils.isBlank(codeInRequest)){
            throw new ValidateCodeException("验证码不能为空");
        }
        if(codeInSession == null){
            throw new ValidateCodeException("验证码不存在");
        }
        //判断验证码时间是否过期
        if(codeInSession.isExpried()){
            sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
            throw new ValidateCodeException("验证码已经过期");
        }
        //比对验证码
        if(!StringUtils.equalsIgnoreCase(codeInSession.getCode(),codeInRequest)){
            throw new ValidateCodeException("验证码不匹配");
        }
        sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
    }

    public AuthenticationFailureHandler getAuthenticationFailureHandler() {
        return authenticationFailureHandler;
    }

    public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
        this.authenticationFailureHandler = authenticationFailureHandler;
    }


    public SecurityProperties getSecurityProperties() {
        return securityProperties;
    }

    public void setSecurityProperties(SecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
    }
}
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private BrowserAuthenticationSuccessHandler browserAuthenticationSuccessHandler;

    @Autowired
    private BrowserAuthenticationFailureHandler browserAuthenticationFailureHandler;


    @Autowired
    private SessionRegistry sessionRegistry;

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    //密码加密
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ValidateCodeFilter validateCodeFilter=new ValidateCodeFilter();
        //处理失败异常
        validateCodeFilter.setAuthenticationFailureHandler(browserAuthenticationFailureHandler);
        //把配置放进去
        validateCodeFilter.setSecurityProperties(securityProperties);
        //初始化方法
        validateCodeFilter.afterPropertiesSet();

        http
                .csrf().disable()//关闭跨站防护
                .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)//在登陆拦截之前添加验证码拦截器
                .authorizeRequests()//下面授权配置
                    .antMatchers("/login",securityProperties.getBrowser().getLoginPage(),"/code/image").permitAll()//login请求除外不需要认证
                    .anyRequest().authenticated()//所有请求都需要身份认证
                .and()
                    .formLogin() //表单登陆页
                    .loginPage("/login")//登陆页面
                    .loginProcessingUrl("/authentication/form")//自定义form表单登陆提交地址默认是/login
                    .successHandler(browserAuthenticationSuccessHandler)//自定义登陆成功后返回json信息
                    .failureHandler(browserAuthenticationFailureHandler)//自定义登陆失败返回json
                .and()
                    .sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry).expiredUrl("/login");//用户只能登陆一次

    }
}

配置文件

    code:
      #image下面的属性有width,height,length,expreIn,url
      image:
        length: 5
        url: /user/*

验证码生成逻辑可配置

public interface ValidateCodeGenerator {
    ImageCode generate(ServletWebRequest request);
}
public class ImageCodeGenerator implements ValidateCodeGenerator{

    @Autowired
    private SecurityProperties securityProperties;

    @Override
    public ImageCode generate(ServletWebRequest request) {
        //先从请求中获取如果获取不到从配置文件获取
        int width = ServletRequestUtils.getIntParameter(request.getRequest(),"width",securityProperties.getCode().getImage().getWidth());
        int height = ServletRequestUtils.getIntParameter(request.getRequest(),"height",securityProperties.getCode().getImage().getHeight());

        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

        Graphics g = image.getGraphics();

        Random random = new Random();

        g.setColor(getRandColor(200, 250));
        g.fillRect(0, 0, width, height);
        g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
        g.setColor(getRandColor(160, 200));
        for (int i = 0; i < 155; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int xl = random.nextInt(12);
            int yl = random.nextInt(12);
            g.drawLine(x, y, x + xl, y + yl);
        }

        String sRand = "";
        for (int i = 0; i < securityProperties.getCode().getImage().getLength(); i++) {
            String rand = String.valueOf(random.nextInt(10));
            sRand += rand;
            g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
            g.drawString(rand, 13 * i + 6, 16);
        }

        g.dispose();

        return new ImageCode(image, sRand, securityProperties.getCode().getImage().getExpreIn());
    }

    /**
     * 生成随机背景条纹
     *
     * @param fc
     * @param bc
     * @return
     */
    private Color getRandColor(int fc, int bc) {
        Random random = new Random();
        if (fc > 255) {
            fc = 255;
        }
        if (bc > 255) {
            bc = 255;
        }
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }

    public SecurityProperties getSecurityProperties() {
        return securityProperties;
    }

    public void setSecurityProperties(SecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
    }
}

@Configuration
public class ValidateCodeBeanConfig {
    @Autowired
    private SecurityProperties securityProperties;

    @Bean
    @ConditionalOnMissingBean(name = "imageCodeGenerator")//在spring加载bean时会先查找有没有名字为imageCodeGenerator
    public ValidateCodeGenerator imageCodeGenerator(){
        ImageCodeGenerator codeGenerator=new ImageCodeGenerator();
        codeGenerator.setSecurityProperties(securityProperties);
        return codeGenerator;
    }
}
@RestController
public class ValidateCodeController {


    public static final String SESSION_KEY="SESSION_KEY_IMAGE_CODE";

    private SessionStrategy sessionStrategy=new HttpSessionSessionStrategy();


    @Autowired
    private ValidateCodeGenerator imageCodeGenerator;

    @RequestMapping(value = "/code/image",method = RequestMethod.GET)
    public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //生成验证码对象
        ImageCode imageCode=imageCodeGenerator.generate(new ServletWebRequest(request));
        //将ImageCode对象保存在session中
        sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode);
        //返回到页面
        ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream());
    }
}

如果想创建自己的验证码生成逻辑


image.png
/**
 * 从类可以重写生成验证码的逻辑替换默认逻辑
 */
@Component(value = "imageCodeGenerator")
public class demoImageCodeGenerator implements ValidateCodeGenerator {
    @Override
    public ImageCode generate(ServletWebRequest request) {
        System.out.printf("自定义验证码");
        return null;
    }
}

记住我功能实现

基本原理

image.png

前端登陆页面传值名称必须叫emember-me

<input  type="checkbox" value="true" name="remember-me" >记住密码

配置可配置记住密码过期时间

public class BrowserProperties {

    //自定义登陆页面
    private String loginPage = "/defaultLogin.html"; //如果用户没有配置登陆页面走默认

    //自定义处理登陆请求默认异步
    private LoginType loginType = LoginType.JSON;

    //设置记住密码过期时间默认1小时
    private int rememberMeSeconds = 3600;


    public String getLoginPage() {
        return loginPage;
    }

    public void setLoginPage(String loginPage) {
        this.loginPage = loginPage;
    }

    public LoginType getLoginType() {
        return loginType;
    }

    public void setLoginType(LoginType loginType) {
        this.loginType = loginType;
    }

    public int getRememberMeSeconds() {
        return rememberMeSeconds;
    }

    public void setRememberMeSeconds(int rememberMeSeconds) {
        this.rememberMeSeconds = rememberMeSeconds;
    }
}

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    //自定义配置文件
    @Autowired
    private SecurityProperties securityProperties;

    //处理登陆成功
    @Autowired
    private BrowserAuthenticationSuccessHandler browserAuthenticationSuccessHandler;

    //处理登陆失败
    @Autowired
    private BrowserAuthenticationFailureHandler browserAuthenticationFailureHandler;

    //数据源
    @Autowired
    private DataSource dataSource;

    //登陆实现
    @Autowired
    private UserDetailsService userDetailsService;


    @Autowired
    private SessionRegistry sessionRegistry;

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    //密码加密
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    //记住密码
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        //启动自动创建persistent_logins表
        //tokenRepository.setCreateTableOnStartup(true);
        return tokenRepository;
    }



    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //验证码拦截器
        ValidateCodeFilter validateCodeFilter=new ValidateCodeFilter();
        //处理失败异常
        validateCodeFilter.setAuthenticationFailureHandler(browserAuthenticationFailureHandler);
        //把配置放进去
        validateCodeFilter.setSecurityProperties(securityProperties);
        //初始化方法
        validateCodeFilter.afterPropertiesSet();

        http
                .csrf().disable()//关闭跨站防护
                .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)//在登陆拦截之前添加验证码拦截器
                .authorizeRequests()//下面授权配置
                    .antMatchers("/login",securityProperties.getBrowser().getLoginPage(),"/code/image").permitAll()//login请求除外不需要认证
                    .anyRequest().authenticated()//所有请求都需要身份认证
                    .and()
                .formLogin() //表单登陆
                    .loginPage("/login")//登陆判断页面
                    .loginProcessingUrl("/authentication/form")//自定义form表单登陆提交地址默认是/login
                    .successHandler(browserAuthenticationSuccessHandler)//自定义登陆成功后返回json信息
                    .failureHandler(browserAuthenticationFailureHandler)//自定义登陆失败返回json
                    .and()
                .rememberMe() //记住密码
                    .tokenRepository(persistentTokenRepository())
                    .tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds()) //失效时间
                    .userDetailsService(userDetailsService)
                    .and()
                .sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry).expiredUrl("/login");//用户只能登陆一次

    }
}

创建数据库表记录token与过期时间

CREATE TABLE `persistent_logins` (
  `username` varchar(64) NOT NULL,
  `series` varchar(64) NOT NULL,
  `token` varchar(64) NOT NULL,
  `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

短信验证码

短信验证码接口开发

public class ValidateCode implements Serializable {
    //随机数
    private String code;
    //失效时间
    private LocalDateTime exireTime;

    public ValidateCode(String code, int exireIn) {
        this.code = code;
        //当前时间加上过期秒数
        this.exireTime = LocalDateTime.now().plusSeconds(exireIn);
    }

    public ValidateCode(String code, LocalDateTime exireTime) {
        this.code = code;
        //当前时间加上过期秒数
        this.exireTime = exireTime;

    }
    //判断时间是否过期
    public boolean isExpried(){
        return LocalDateTime.now().isAfter(exireTime);
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public LocalDateTime getExireTime() {
        return exireTime;
    }

    public void setExireTime(LocalDateTime exireTime) {
        this.exireTime = exireTime;
    }
}

短信验证码生成

@Component("smsCodeGenerator")
public class SmsCodeGenerator implements ValidateCodeGenerator{

    @Autowired
    private SecurityProperties securityProperties;

    /**
     * 生成验证码
     * @param request
     * @return
     */
    @Override
    public ValidateCode generate(ServletWebRequest request) {
        String code= RandomStringUtils.randomNumeric(securityProperties.getCode().getSms().getLength());

        return new ValidateCode(code,securityProperties.getCode().getSms().getExpreIn());
    }


}

短信验证码发送

//短信验证码发送
public interface SmsCodeSender {
    void send(String mobile,String code);
}
/**
 * 默认短信发送器
 */
public class DefaultSmsCodeSender implements SmsCodeSender{

    private Logger logger = LoggerFactory.getLogger(getClass());
    @Override
    public void send(String mobile, String code) {
        logger.info("向手机"+mobile+"发送短信验证码"+code);
    }
}

接口生成

@RestController
public class ValidateCodeController {


    public static final String SESSION_KEY="SESSION_KEY_IMAGE_CODE";

    private SessionStrategy sessionStrategy=new HttpSessionSessionStrategy();


    @Autowired
    private ValidateCodeGenerator imageCodeGenerator;

    @Autowired
    private ValidateCodeGenerator smsCodeGenerator;

    //发送短信
    @Autowired
    private SmsCodeSender smsCodeSender;

    //图形验证码
    @RequestMapping(value = "/code/image",method = RequestMethod.GET)
    public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //生成验证码对象
        ImageCode imageCode= (ImageCode) imageCodeGenerator.generate(new ServletWebRequest(request));
        //将ImageCode对象保存在session中
        sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode);
        //返回到页面
        ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream());
    }

    //短信验证码
    @RequestMapping(value = "/code/sms",method = RequestMethod.GET)
    public void createSmsCode(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletRequestBindingException {
        //生成验证码对象
        ValidateCode validateCode=smsCodeGenerator.generate(new ServletWebRequest(request));
        //将validateCode对象保存在session中
        sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,validateCode);
        String mobile= ServletRequestUtils.getRequiredStringParameter(request,"mobile");
        //发送短信
        smsCodeSender.send(mobile,validateCode.getCode());

    }
}

短信登录开发

image.png

public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    // ~ Instance fields
    // ================================================================================================

    private final Object principal;

    // ~ Constructors
    // ===================================================================================================

    /**
     * This constructor can be safely used by any code that wishes to create a
     * <code>UsernamePasswordAuthenticationToken</code>, as the {@link #isAuthenticated()}
     * will return <code>false</code>.
     *
     */
    public SmsCodeAuthenticationToken(String mobile) {
        super(null);
        this.principal = mobile;
        setAuthenticated(false);
    }

    /**
     * This constructor should only be used by <code>AuthenticationManager</code> or
     * <code>AuthenticationProvider</code> implementations that are satisfied with
     * producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
     * authentication token.
     *
     * @param principal
     * @param authorities
     */
    public SmsCodeAuthenticationToken(Object principal,
                                      Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        super.setAuthenticated(true); // must use super, as we override
    }

    // ~ Methods
    // ========================================================================================================

    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");
        }

        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
    }
}

public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    public static final String GUOSH_FORM_MOBILE_KEY = "mobile";

    private String mobileParameter = GUOSH_FORM_MOBILE_KEY;
    private boolean postOnly = true;

    public SmsCodeAuthenticationFilter() {
        super(new AntPathRequestMatcher("/authentication/mobile", "POST"));
    }

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String mobile = this.obtainMobile(request);

            if (mobile == null) {
                mobile = "";
            }
            mobile = mobile.trim();
            SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

    /**
     * 获取手机号
     */
    protected String obtainMobile(HttpServletRequest request) {
        return request.getParameter(this.mobileParameter);
    }

    protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }

    public void setMobileParameter(String mobileParameter) {
        Assert.hasText(mobileParameter, "mobile parameter must not be empty or null");
        this.mobileParameter = mobileParameter;
    }



    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }

    public final String getMobileParameter() {
        return this.mobileParameter;
    }


}


public class SmsCodeAuthenticationProvider implements AuthenticationProvider {

    private UserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        SmsCodeAuthenticationToken authenticationToken=(SmsCodeAuthenticationToken)authentication;
        UserDetails user=userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());
        if(user==null){
            throw new InternalAuthenticationServiceException("无法获取用户信息");
        }
        SmsCodeAuthenticationToken authenticationResult=new SmsCodeAuthenticationToken(user,user.getAuthorities());
        authenticationResult.setDetails(authenticationToken.getDetails());
        return authenticationResult;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return SmsCodeAuthenticationToken.class.isAssignableFrom(aClass);
    }

    public UserDetailsService getUserDetailsService() {
        return userDetailsService;
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
}
@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        //短信验证过滤器
        SmsCodeAuthenticationFilter smsCodeAuthenticationFilter=new SmsCodeAuthenticationFilter();
        smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        //成功处理器
        smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
        //失败处理器
        smsCodeAuthenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler);


        SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
        smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);

        http.authenticationProvider(smsCodeAuthenticationProvider).addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }

}

package com.guosh.security.core.validate.code.web.filter;

import com.guosh.security.core.properties.SecurityProperties;
import com.guosh.security.core.validate.code.ValidateCode;
import com.guosh.security.core.validate.code.image.ImageCode;
import com.guosh.security.core.validate.code.web.exception.ValidateCodeException;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

public class SmsCodeFilter extends OncePerRequestFilter implements InitializingBean {

    //登陆失败的处理器
    private AuthenticationFailureHandler authenticationFailureHandler;

    private SessionStrategy sessionStrategy=new HttpSessionSessionStrategy();

    private Set<String>urls = new HashSet<>();

    private SecurityProperties securityProperties;

    private AntPathMatcher antPathMatcher=new AntPathMatcher();

    //初始化加载
    @Override
    public void afterPropertiesSet() throws ServletException {
        super.afterPropertiesSet();
        //把要拦截的分割转换数组
        if(StringUtils.isNotBlank(securityProperties.getCode().getSms().getUrl())){
            String[]configUrls=StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getCode().getSms().getUrl(),",");
            for (String configUrl:configUrls) {
                urls.add(configUrl);
            }
        }
        urls.add("/authentication/mobile");
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            boolean action = false;
            //用antPathMatcher工具栏判断路径是否符合
            for (String url : urls) {
                if (antPathMatcher.match(url, request.getServletPath())) {
                    action = true;
                }
            }

            //必须是登陆请求并且是post请求
            if (action) {
                try {
                    //校验验证码
                    validate(new ServletWebRequest(request));
                } catch (ValidateCodeException e) {
                    authenticationFailureHandler.onAuthenticationFailure(request, response, e);
                    return;
                }
            }

        filterChain.doFilter(request,response);
    }

    private void validate(ServletWebRequest request) throws ServletRequestBindingException {
        //从session获取验证码
        ValidateCode codeInSession= (ValidateCode) sessionStrategy.getAttribute(request, "SESSION_KEY_FOR_CODE_SMS");
        //从请求里获取验证码
        String codeInRequest=ServletRequestUtils.getStringParameter(request.getRequest(),"smsCode");

        if(StringUtils.isBlank(codeInRequest)){
            throw new ValidateCodeException("验证码不能为空");
        }
        if(codeInSession == null){
            throw new ValidateCodeException("验证码不存在");
        }
        //判断验证码时间是否过期
        if(codeInSession.isExpried()){
            sessionStrategy.removeAttribute(request, "SESSION_KEY_FOR_CODE_SMS");
            throw new ValidateCodeException("验证码已经过期");
        }
        //比对验证码
        if(!StringUtils.equalsIgnoreCase(codeInSession.getCode(),codeInRequest)){
            throw new ValidateCodeException("验证码不匹配");
        }
        sessionStrategy.removeAttribute(request, "SESSION_KEY_FOR_CODE_SMS");
    }

    public AuthenticationFailureHandler getAuthenticationFailureHandler() {
        return authenticationFailureHandler;
    }

    public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
        this.authenticationFailureHandler = authenticationFailureHandler;
    }


    public SecurityProperties getSecurityProperties() {
        return securityProperties;
    }

    public void setSecurityProperties(SecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
    }
}

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    //自定义配置文件
    @Autowired
    private SecurityProperties securityProperties;

    //处理登陆成功
    @Autowired
    private BrowserAuthenticationSuccessHandler browserAuthenticationSuccessHandler;

    //处理登陆失败
    @Autowired
    private BrowserAuthenticationFailureHandler browserAuthenticationFailureHandler;


    @Autowired
    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;

    //数据源
    @Autowired
    private DataSource dataSource;

    //登陆实现
    @Autowired
    private UserDetailsService userDetailsService;


    @Autowired
    private SessionRegistry sessionRegistry;

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    //密码加密
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    //记住密码
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        //启动自动创建persistent_logins表
        //tokenRepository.setCreateTableOnStartup(true);
        return tokenRepository;
    }



    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //验证码拦截器
        ValidateCodeFilter validateCodeFilter=new ValidateCodeFilter();
        //处理失败异常
        validateCodeFilter.setAuthenticationFailureHandler(browserAuthenticationFailureHandler);
        //把配置放进去
        validateCodeFilter.setSecurityProperties(securityProperties);
        //初始化方法
        validateCodeFilter.afterPropertiesSet();

        //验证码拦截器
        SmsCodeFilter smsCodeFilter=new SmsCodeFilter();
        //处理失败异常
        smsCodeFilter.setAuthenticationFailureHandler(browserAuthenticationFailureHandler);
        //把配置放进去
        smsCodeFilter.setSecurityProperties(securityProperties);
        //初始化方法
        smsCodeFilter.afterPropertiesSet();


        http
                .csrf().disable()//关闭跨站防护
                .apply(smsCodeAuthenticationSecurityConfig) //短信登陆配置
                    .and()
                .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)//在登陆拦截之前添加验证码拦截器
                .addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class)//在登陆拦截之前添加验证码拦截器
                .authorizeRequests()//下面授权配置
                    .antMatchers("/login",securityProperties.getBrowser().getLoginPage(),"/code/*").permitAll()//login请求除外不需要认证
                    .anyRequest().authenticated()//所有请求都需要身份认证
                    .and()
                .formLogin() //表单登陆
                    .loginPage("/login")//登陆判断页面
                    .loginProcessingUrl("/authentication/form")//自定义form表单登陆提交地址默认是/login
                    .successHandler(browserAuthenticationSuccessHandler)//自定义登陆成功后返回json信息
                    .failureHandler(browserAuthenticationFailureHandler)//自定义登陆失败返回json
                    .and()
                .rememberMe() //记住密码
                    .tokenRepository(persistentTokenRepository())
                    .tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds()) //失效时间
                    .userDetailsService(userDetailsService)
                    .and()
                .sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry).expiredUrl("/login");//用户只能登陆一次

    }
}

相关文章

网友评论

    本文标题:Spring Security项目基于表单登陆(三)

    本文链接:https://www.haomeiwen.com/subject/sissbqtx.html