spring-security认证过程的分析及自定义登录

来源:互联网 发布:java线程池实现方式 编辑:程序博客网 时间:2024/05/21 09:00

首先spring-security配置认证过滤器,它是spring-security处理业务的入口。用户如果不重写过滤器,使用默认的过滤器UsernamePasswordAuthenticationFilter。它继承了抽象类AbstractAuthenticationProcessingFilter,该类注入了authenticationManager属性,配置security的认证管理器。

<beans:bean id="myLoginFilter" class="com.yinhai.modules.security.spring.app.filter.Ta3AuthenticationFilter">          <!--认证管理器-->    <beans:property name="authenticationManager" ref="myAuthenticationManager" />      <!-- 验证成功后执行扩展的处理 -->    <beans:property name="authenticationSuccessHandler" ref="taOnAuthenticationSuccessHandler" />    <!-- 验证失败后执行扩展的处理 -->    <beans:property name="authenticationFailureHandler" ref="taAuthenticationFailureHandler" />    <beans:property name="filterProcessesUrl" value="/j_spring_security_check" />    <beans:property name="userBpo" ref="userBpo" />    <beans:property name="sessionAuthenticationStrategy" ref="sas"></beans:property></beans:bean>

过滤器UsernamePasswordAuthenticationFilter拿到用户名密码创建一个UsernamePasswordAuthenticationToken,通过认证管理器进行认证代码如下

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 username = this.obtainUsername(request);        String password = this.obtainPassword(request);        if (username == null) {            username = "";        }        if (password == null) {            password = "";        }        username = username.trim();        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);        this.setDetails(request, authRequest);        return this.getAuthenticationManager().authenticate(authRequest);    }}

认证管理器配置如下

<authentication-manager alias="myAuthenticationManager">      <authentication-provider user-service-ref="taUserDetailsService">          <password-encoder ref="md5Encoder">           <salt-source ref="saltSource"/>            </password-encoder>      </authentication-provider>  </authentication-manager>

认证管理器是通过接口AuthenticationManager处理的

public interface AuthenticationManager {    Authentication authenticate(Authentication var1) throws AuthenticationException;}

ProviderManager类实现了这个接口,它的认证代码如下

public Authentication authenticate(Authentication authentication) throws AuthenticationException {        Class<? extends Authentication> toTest = authentication.getClass();        AuthenticationException lastException = null;        Authentication result = null;        boolean debug = logger.isDebugEnabled();        Iterator var6 = this.getProviders().iterator();        while(var6.hasNext()) {            AuthenticationProvider provider = (AuthenticationProvider)var6.next();            if (provider.supports(toTest)) {                if (debug) {                    logger.debug("Authentication attempt using " + provider.getClass().getName());                }                try {                    result = provider.authenticate(authentication);                    if (result != null) {                        this.copyDetails(authentication, result);                        break;                    }                } catch (AccountStatusException var11) {                    this.prepareException(var11, authentication);                    throw var11;                } catch (InternalAuthenticationServiceException var12) {                    this.prepareException(var12, authentication);                    throw var12;                } catch (AuthenticationException var13) {                    lastException = var13;                }            }        }        if (result == null && this.parent != null) {            try {                result = this.parent.authenticate(authentication);            } catch (ProviderNotFoundException var9) {                ;            } catch (AuthenticationException var10) {                lastException = var10;            }        }        if (result != null) {            if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {                ((CredentialsContainer)result).eraseCredentials();            }            this.eventPublisher.publishAuthenticationSuccess(result);            return result;        } else {            if (lastException == null) {                lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));            }            this.prepareException((AuthenticationException)lastException, authentication);            throw lastException;        }    }
AuthenticationProvider provider = (AuthenticationProvider)var6.next();result = provider.authenticate(authentication);

看以上代码,认证过程继续调用AuthenticationProvider,它是一个接口,调用抽象类AbstractUserDetailsAuthenticationProvider进行认证。

user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);

DaoAuthenticationProvider类继承了抽象类AbstractUserDetailsAuthenticationProvider,重写了获取用户的方法,通过配置中的user-service-ref,调用获取UserDetails的方法。

loadedUser = this.getUserDetailsService().loadUserByUsername(username);

接下来对用户进行验证如果没有配置password-encoder,默认调用PlaintextPasswordEncoder进行密码的对比。否则调用用户配置的密码加密类进行密码比对。

protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {        Object salt = null;        if (this.saltSource != null) {            salt = this.saltSource.getSalt(userDetails);        }        if (authentication.getCredentials() == null) {            this.logger.debug("Authentication failed: no credentials provided");            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));        } else {            String presentedPassword = authentication.getCredentials().toString();            if (!this.passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {                this.logger.debug("Authentication failed: password does not match stored value");                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));            }        }    }

基于上述分析,现我们有如下业务需求,通过qq等第三方登录,绑定系统的账号,通过绑定账号直接进行security的登录,那么我们该怎么做呢?主要代码如下

String userName = request.getParameter("userName");String pwd = request.getParameter("pwd");userName = userName.trim();UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, pwd);Authentication authentication = authenticationManager.authenticate(authRequest);             //调用loadUserByUsername  SecurityContextHolder.getContext().setAuthentication(authentication);HttpSession session = request.getSession();session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());// 这个非常重要,否则验证后将无法登陆

通过绑定用户查询数据库,获取用户密码,手动创建一个UsernamePasswordAuthenticationToken,调用配置的认证管理器,返回一个认证令牌,注册到security容器中。即可。

这里有一个问题,如果user-service-ref配置了密码加密方式,那么数据库中存入的用户密码为加密后的密码,调用认证管理器会把数据库查询的密码再使用加密方式加密一次,这样密码比对就会失败。

解决这个问题,重写认证管理器。

public Authentication authenticate(Authentication auth)                throws AuthenticationException {            String username = auth.getName();            UserDetails userDetails = taUserDetailsService.loadUserByUsername(username);            Object principal = userDetails;            UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, auth.getCredentials(), this.authoritiesMapper.mapAuthorities(userDetails.getAuthorities()));            result.setDetails(auth.getDetails());            return result;        }
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(loginId, password);        Authentication authentication = simpleAuthenticationManager.authenticate(authRequest);        SecurityContextHolder.getContext().setAuthentication(authentication);

这样,就不会使用配置的认证管理器,进行加密验证了。

阅读全文
0 0