文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Spring Security如何实现登录验证

2023-06-26 04:48

关注

这篇文章主要讲解了“Spring Security如何实现登录验证”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Spring Security如何实现登录验证”吧!

一、理论知识

我们先思考一下这个流程大致是如何的?

大致流程就是如此。从这个流程中我们可以知道,需要重写的组件有以下几个:

接下来,我是模仿着源码写出我的代码,建议大家可以在使用的时候,多去看看,我这里去除了一些不是和这个相关的代码。

来吧!!

二、EmailCodeAuthenticationFilter

我们需要重写的 EmailCodeAuthenticationFilter,实际继承了AbstractAuthenticationProcessingFilter抽象类,我们不会写,可以先看看它的默认实现UsernamePasswordAuthenticationFilter是怎么样的吗,抄作业这是大家的强项的哈。

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {        public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";    private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login",            "POST");    //从前台传过来的参数    private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;    private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;    private boolean postOnly = true;        //  初始化一个用户密码 认证过滤器  默认的登录uri 是 /login 请求方式是POST    public UsernamePasswordAuthenticationFilter() {        super(DEFAULT_ANT_PATH_REQUEST_MATCHER);    }    public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {        super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);    }        @Override    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());        }        String username = obtainUsername(request);        username = (username != null) ? username : "";        username = username.trim();        String password = obtainPassword(request);        password = (password != null) ? password : "";        //生成 UsernamePasswordAuthenticationToken 稍后交由AuthenticationManager中的authenticate进行认证        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);        // 可以放一些其他信息进去        setDetails(request, authRequest);        return this.getAuthenticationManager().authenticate(authRequest);    }    @Nullable    protected String obtainPassword(HttpServletRequest request) {        return request.getParameter(this.passwordParameter);    }    @Nullable    protected String obtainUsername(HttpServletRequest request) {        return request.getParameter(this.usernameParameter);    }    protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));    }    //set、get方法}

接下来我们就抄个作业哈:

package com.crush.security.auth.email_code;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.authentication.AuthenticationServiceException;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 org.springframework.security.web.util.matcher.RequestMatcher;import javax.servlet.FilterChain;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import java.io.IOException;import java.util.ArrayList;public class EmailCodeAuthenticationFilter  extends AbstractAuthenticationProcessingFilter {        private final String DEFAULT_EMAIL_NAME="email";    private final String DEFAULT_EMAIL_CODE="e_code";    @Autowired    @Override    public void setAuthenticationManager(AuthenticationManager authenticationManager) {        super.setAuthenticationManager(authenticationManager);    }        private boolean postOnly = true;        public EmailCodeAuthenticationFilter() {        super(new AntPathRequestMatcher("/email/login","POST"));    }        @Override    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {        if(postOnly && !request.getMethod().equals("POST") ){            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());        }else{            String email = getEmail(request);            if(email == null){                email = "";            }            email = email.trim();            //如果 验证码不相等 故意让token出错 然后走springsecurity 错误的流程            boolean flag = checkCode(request);            //封装 token            EmailCodeAuthenticationToken token = new EmailCodeAuthenticationToken(email,new ArrayList<>());            this.setDetails(request,token);            //交给 manager 发证            return this.getAuthenticationManager().authenticate(token);        }    }        public void setDetails(HttpServletRequest request , EmailCodeAuthenticationToken token ){        token.setDetails(this.authenticationDetailsSource.buildDetails(request));    }        public String getEmail(HttpServletRequest request ){        String result=  request.getParameter(DEFAULT_EMAIL_NAME);        return result;    }        public boolean checkCode(HttpServletRequest request ){        String code1 = request.getParameter(DEFAULT_EMAIL_CODE);        System.out.println("code1**********"+code1);        // TODO 另外再写一个链接 生成 验证码 那个验证码 在生成的时候  存进redis 中去        //TODO  这里的验证码 写在Redis中, 到时候取出来判断即可 验证之后 删除验证码        if(code1.equals("123456")){            return true;        }        return false;    }// set、get方法...}

三、EmailCodeAuthenticationToken

我们EmailCodeAuthenticationToken是继承AbstractAuthenticationToken的,按照同样的方式,我们接着去看看AbstractAuthenticationToken的默认实现是什么样的就行了。

public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;    // 这里指的账号密码哈    private final Object principal;    private Object credentials;        public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {        super(null);        this.principal = principal;        this.credentials = credentials;        setAuthenticated(false);    }        public UsernamePasswordAuthenticationToken(Object principal, Object credentials,            Collection<? extends GrantedAuthority> authorities) {        super(authorities);        this.principal = principal;        this.credentials = credentials;        super.setAuthenticated(true); // must use super, as we override    }    @Override    public Object getCredentials() {        return this.credentials;    }    @Override    public Object getPrincipal() {        return this.principal;    }    @Override    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {        Assert.isTrue(!isAuthenticated,                "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");        super.setAuthenticated(false);    }    @Override    public void eraseCredentials() {        super.eraseCredentials();        this.credentials = null;    }}

日常抄作业哈:

public class EmailCodeAuthenticationToken extends AbstractAuthenticationToken {        private final Object principal;    public EmailCodeAuthenticationToken(Object principal) {        super((Collection) null);        this.principal = principal;        setAuthenticated(false);    }    public EmailCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {        super(authorities);        this.principal = principal;        super.setAuthenticated(true);    }    @Override    public Object getCredentials() {        return null;    }    @Override    public Object getPrincipal() {        return this.principal;    }    @Override    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {        if (isAuthenticated) {            throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");        } else {            super.setAuthenticated(false);        }    }}

这个很简单的哈

四、EmailCodeAuthenticationProvider

自定义的EmailCodeAuthenticationProvider是实现了AuthenticationProvider接口,抄作业就得学会看看源码。我们接着来。

4.1、先看看AbstractUserDetailsAuthenticationProvider,我们再来模仿

AuthenticationProvider 接口有很多实现类,不一一说明了,直接看我们需要看的AbstractUserDetailsAuthenticationProvider, 该类旨在响应 UsernamePasswordAuthenticationToken 身份验证请求。但是它是一个抽象类,但其实就一个步骤在它的实现类中实现的,很简单,稍后会讲到。

在这个源码中我把和检查相关的一些操作都给删除,只留下几个重点,我们一起来看一看哈。

//该类旨在响应UsernamePasswordAuthenticationToken身份验证请求。public abstract class AbstractUserDetailsAuthenticationProvider        implements AuthenticationProvider, InitializingBean, MessageSourceAware {    protected final Log logger = LogFactory.getLog(getClass());    private UserCache userCache = new NullUserCache();    @Override    public Authentication authenticate(Authentication authentication) throws AuthenticationException {        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,                () -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",                        "Only UsernamePasswordAuthenticationToken is supported"));        //获取用户名        String username = determineUsername(authentication);        //判断缓存中是否存在        boolean cacheWasUsed = true;        UserDetails user = this.userCache.getUserFromCache(username);        if (user == null) {            cacheWasUsed = false;            try {                // 缓存中没有 通过字类实现的retrieveUser 从数据库进行检索,返回一个 UserDetails 对象                user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);            }            catch (UsernameNotFoundException ex) {                this.logger.debug("Failed to find user '" + username + "'");                if (!this.hideUserNotFoundExceptions) {                    throw ex;                }                throw new BadCredentialsException(this.messages                        .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));            }            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");        }        try {            //进行相关检查  因为可能是从缓存中取出来的 并非是最新的            this.preAuthenticationChecks.check(user);            additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);        }        catch (AuthenticationException ex) {            if (!cacheWasUsed) {                throw ex;            }            // 没有通过检查, 重新检索最新的数据            cacheWasUsed = false;            user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);            this.preAuthenticationChecks.check(user);            additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);        }        // 再次进行检查        this.postAuthenticationChecks.check(user);        // 存进缓存中去        if (!cacheWasUsed) {            this.userCache.putUserInCache(user);        }        Object principalToReturn = user;        if (this.forcePrincipalAsString) {            principalToReturn = user.getUsername();        }        //创建一个可信的身份令牌返回        return createSuccessAuthentication(principalToReturn, authentication, user);    }    private String determineUsername(Authentication authentication) {        return (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();    }        protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,            UserDetails user) {        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,                authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));        result.setDetails(authentication.getDetails());        this.logger.debug("Authenticated user");        return result;    }        protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)            throws AuthenticationException;    //...        //简而言之:当然有时候我们有多个不同的 `AuthenticationProvider`,它们分别支持不同的 `Authentication`对象,那么当一个具体的 `AuthenticationProvier`传进入 `ProviderManager`的内部时,就会在 `AuthenticationProvider`列表中挑选其对应支持的provider对相应的 Authentication对象进行验证    @Override    public boolean supports(Class<?> authentication) {        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));    }}

关于 protected abstract UserDetails retrieveUser 的实现,AbstractUserDetailsAuthenticationProvider实现是DaoAuthenticationProvider.

DaoAuthenticationProvider主要操作是两个,第一个是从数据库中检索出相关信息,第二个是给检索出的用户信息进行密码的加密操作。

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {    private UserDetailsService userDetailsService;        @Override    protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)            throws AuthenticationException {        prepareTimingAttackProtection();        try {            // 检索用户,一般我们都会实现 UserDetailsService接口,改为从数据库中检索用户信息 返回安全核心类 UserDetails            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);            if (loadedUser == null) {                throw new InternalAuthenticationServiceException(                        "UserDetailsService returned null, which is an interface contract violation");            }            return loadedUser;        }        catch (UsernameNotFoundException ex) {            mitigateAgainstTimingAttack(authentication);            throw ex;        }        catch (InternalAuthenticationServiceException ex) {            throw ex;        }        catch (Exception ex) {            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);        }    }    @Override    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,            UserDetails user) {        // 判断是否用了密码加密 针对这个点 没有深入 大家好奇可以去查一查这个知识点        boolean upgradeEncoding = this.userDetailsPasswordService != null                && this.passwordEncoder.upgradeEncoding(user.getPassword());        if (upgradeEncoding) {            String presentedPassword = authentication.getCredentials().toString();            String newPassword = this.passwordEncoder.encode(presentedPassword);            user = this.userDetailsPasswordService.updatePassword(user, newPassword);        }        return super.createSuccessAuthentication(principal, authentication, user);    }}

4.2、抄作业啦

看完源码,其实我们如果要重写的话,主要要做到以下几个事情:

重写public boolean supports(Class<?> authentication)方法。

有时候我们有多个不同的 AuthenticationProvider,它们分别支持不同的 Authentication对象,那么当一个具体的 AuthenticationProvier 传进入 ProviderManager的内部时,就会在 AuthenticationProvider列表中挑选其对应支持的 provider 对相应的 Authentication对象进行验证

简单说就是指定AuthenticationProvider验证哪个 Authentication 对象。如指定DaoAuthenticationProvider认证UsernamePasswordAuthenticationToken,

所以我们指定EmailCodeAuthenticationProvider认证EmailCodeAuthenticationToken。

检索数据库,返回一个安全核心类UserDetail。

创建一个经过身份验证的Authentication对象

了解要做什么事情了,我们就可以动手看看代码啦。

@Slf4jpublic class EmailCodeAuthenticationProvider implements AuthenticationProvider {    ITbUserService userService;    public EmailCodeAuthenticationProvider(ITbUserService userService) {        this.userService = userService;    }        @Override    public Authentication authenticate(Authentication authentication) throws AuthenticationException {        if (!supports(authentication.getClass())) {            return null;        }        log.info("EmailCodeAuthentication authentication request: %s", authentication);        EmailCodeAuthenticationToken token = (EmailCodeAuthenticationToken) authentication;        UserDetails user = userService.getByEmail((String) token.getPrincipal());        System.out.println(token.getPrincipal());        if (user == null) {            throw new InternalAuthenticationServiceException("无法获取用户信息");        }        System.out.println(user.getAuthorities());        EmailCodeAuthenticationToken result =                new EmailCodeAuthenticationToken(user, user.getAuthorities());                        result.setDetails(token.getDetails());        return result;    }    @Override    public boolean supports(Class<?> aClass) {        return EmailCodeAuthenticationToken.class.isAssignableFrom(aClass);    }}

五、在配置类中进行配置

主要就是做下面几件事:将过滤器、认证器注入到spring中
将登录成功处理、登录失败处理器注入到Spring中,或者在自定义过滤器中对登录成功和失败进行处理。
添加到过滤链中

    @Bean    public EmailCodeAuthenticationFilter emailCodeAuthenticationFilter() {        EmailCodeAuthenticationFilter emailCodeAuthenticationFilter = new EmailCodeAuthenticationFilter();        emailCodeAuthenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);        emailCodeAuthenticationFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);        return emailCodeAuthenticationFilter;    }    @Bean    public EmailCodeAuthenticationProvider emailCodeAuthenticationProvider() {        return new EmailCodeAuthenticationProvider(userService);    }        @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());        //authenticationProvider 根据传入的自定义AuthenticationProvider添加身份AuthenticationProvider 。        auth.authenticationProvider(emailCodeAuthenticationProvider());    }
.and()    .authenticationProvider(emailCodeAuthenticationProvider())    .addFilterBefore(emailCodeAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class)    .authenticationProvider(mobileCodeAuthenticationProvider())    .addFilterBefore(mobileCodeAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class)

感谢各位的阅读,以上就是“Spring Security如何实现登录验证”的内容了,经过本文的学习后,相信大家对Spring Security如何实现登录验证这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯