前言:
现时看官们对“消息认证的实现过程包括”大致比较关心,咱们都想要知道一些“消息认证的实现过程包括”的相关内容。那么小编同时在网摘上汇集了一些对于“消息认证的实现过程包括””的相关资讯,希望看官们能喜欢,咱们快快来学习一下吧!前面使用过滤器的方式实现了带图形验证码的验证功能,属于Servlet层面,简单、易理解。其实,Spring Security还提供了一种更优雅的实现图形验证码的方式,即自定义认证。
1 认识AuthenticationProvider
在学习Spring Security的自定义认证之前,有必要了解Spring Security是如何灵活集成多种认证技术的。
我们所面对的系统中的用户,在Spring Security中被称为主体(principal)。主体包含了所有能够经过验证而获得系统访问权限的用户、设备或其他系统。主体的概念实际上来自Java Security,Spring Security通过一层包装将其定义为一个Authentication。
package org.springframework.security.core;import java.io.Serializable;import java.security.Principal;import java.util.Collection;public interface Authentication extends Principal, Serializable { Collection<? extends GrantedAuthority> getAuthorities(); Object getCredentials(); Object getDetails(); Object getPrincipal(); boolean isAuthenticated(); void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;}
Authentication中包含主体权限列表、主体凭据、主体详细信息,以及主体是否验证成功等信息。由于大部分场景下身份验证都是基于用户名和密码进行的,所以Spring Security提供了一个UsernamePasswordAuthenticationToken用于代指这一类证明(例如,用SSH KEY也可以登录,但它不属于用户名和密码登录这个范畴,如有必要,也可以自定义提供)。在前面使用的表单登录中,每一个登录用户都被包装为一个 UsernamePasswordAuthenticationToken,从而在Spring Security的各个AuthenticationProvider中流动。
AuthenticationProvider被Spring Security定义为一个验证过程。
package org.springframework.security.authentication;import org.springframework.security.core.Authentication;import org.springframework.security.core.AuthenticationException;public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException; boolean supports(Class<?> authentication);}
一次完整的认证可以包含多个AuthenticationProvider,一般由ProviderManager管理。
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean { private List<AuthenticationProvider> providers; public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; AuthenticationException parentException = null; Authentication result = null; Authentication parentResult = null; int currentPosition = 0; int size = this.providers.size(); Iterator var9 = this.getProviders().iterator(); while(var9.hasNext()) { AuthenticationProvider provider = (AuthenticationProvider)var9.next(); if (provider.supports(toTest)) { if (logger.isTraceEnabled()) { Log var10000 = logger; String var10002 = provider.getClass().getSimpleName(); ++currentPosition; var10000.trace(LogMessage.format("Authenticating request with %s (%d/%d)", var10002, currentPosition, size)); } try { result = provider.authenticate(authentication); if (result != null) { this.copyDetails(authentication, result); break; } } catch (InternalAuthenticationServiceException | AccountStatusException var14) { this.prepareException(var14, authentication); throw var14; } catch (AuthenticationException var15) { AuthenticationException ex = var15; lastException = ex; } } } if (result == null && this.parent != null) { try { parentResult = this.parent.authenticate(authentication); result = parentResult; } catch (ProviderNotFoundException var12) { } catch (AuthenticationException var13) { parentException = var13; lastException = var13; } } if (result != null) { if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) { ((CredentialsContainer)result).eraseCredentials(); } if (parentResult == null) { 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}")); } if (parentException == null) { this.prepareException((AuthenticationException)lastException, authentication); } throw lastException; } }}2 自定义AuthenticationProvider
Spring Security提供了多种常见的认证技术,包括但不限于以下几种:
◎ HTTP层面的认证技术,包括HTTP基本认证和HTTP摘要认证两种。
◎ 基于LDAP的认证技术(Lightweight Directory Access Protocol,轻量目录访问协议)。
◎ 聚焦于证明用户身份的OpenID认证技术。
◎ 聚焦于授权的OAuth认证技术。
◎ 系统内维护的用户名和密码认证技术。
其中,使用最为广泛的是由系统维护的用户名和密码认证技术,通常会涉及数据库访问。为了更好地按需定制,Spring Security 并没有直接糅合整个认证过程,而是提供了一个抽象的AuthenticationProvider。
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware { //附加认证过程 protected abstract void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException; //检索用户 protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException; //认证过程 public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> { return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"); }); String username = this.determineUsername(authentication); boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username); if (user == null) { cacheWasUsed = false; try { user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); } catch (UsernameNotFoundException var6) { UsernameNotFoundException ex = var6; 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); this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); } catch (AuthenticationException var7) { AuthenticationException ex = var7; if (!cacheWasUsed) { throw ex; } cacheWasUsed = false; user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); this.preAuthenticationChecks.check(user); this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); } this.postAuthenticationChecks.check(user); if (!cacheWasUsed) { this.userCache.putUserInCache(user); } Object principalToReturn = user; if (this.forcePrincipalAsString) { principalToReturn = user.getUsername(); } return this.createSuccessAuthentication(principalToReturn, authentication, user); }}
在 AbstractUserDetailsAuthenticationProvider中实现了基本的认证流程,通过继承AbstractUserDetailsAuthenticationProvider,并实现retrieveUser和additionalAuthenticationChecks两个抽象方法即可自定义核心认证过程,灵活性非常高。
Spring Security同样提供一个继承自AbstractUserDetailsAuthenticationProvider 的Authenti cationProvider。
DaoAuthenticationProvider的用户信息来源于UserDetailsService,并且整合了密码编码的实现,在前面章节中学习的表单认证就是由DaoAuthenticationProvider提供的。
3 实现图形验证码的AuthenticationProvider
前面我们已经基本了解了Spring Security的认证流程,现在重新回到自定义认证实现图形验证码登录这个具体案例中。由于只是在常规的认证之上增加了图形验证码的校验,其他流程并没有变化,所以只需继承DaoAuthenticationProvider并稍作增添即可。
package com.by.security.provider;import com.by.security.exception.VerificationCodeException;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;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.stereotype.Component;/** * @Author: Cyz * @Description: * @Date: create in 2024/6/4 16:06 */@Componentpublic class MyDaoAuthenticationProvider extends DaoAuthenticationProvider{ public MyDaoAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder){ super.setUserDetailsService(userDetailsService); super.setPasswordEncoder(passwordEncoder); } //在这里获取request 通过request来获取验证码 作比对 @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { MyWebAuthenticationDetails details = (MyWebAuthenticationDetails) authentication.getDetails(); if(!details.isImageCodeIsRight()){ throw new VerificationCodeException(); } super.additionalAuthenticationChecks(userDetails, authentication); }}
在验证流程中添加新的逻辑后似乎有些问题。在additionalAuthenticationChecks中,我们可以得到的参数是来自UserDetailsService的UserDetails,以及根据用户提交的账号信息封装而来的UsernamePasswordAuthenticationToken,而图形验证码的校验必须要有HttpServletRequest对象,因为用户提交的验证码和session存储的验证码都需要从用户的请求中获取,这是否意味着这种实现方式不可行呢?并非如此,Authentication实际上还可以携带账号信息之外的数据。
package org.springframework.security.core;public interface Authentication extends Principal, Serializable { //允许携带任意对象 Object getDetails();}
如果这个数据可以利用,那么难题自然就迎刃而解了。前面提到过,一次完整的认证可以包含多个AuthenticationProvider,这些AuthenticationProvider都是由ProviderManager管理的,而ProviderManager是由UsernamePasswordAuthenticationFilter调用的。也就是说,所有的AuthenticationProvider包含的Authentication都来源于UsernamePasswordAuthenticationFilter。
AbstractAuthenticationProcessingFilter本身并没有设置用户详细信息的流程,而且是通过标准接口AuthenticationDetailsSource构建的,这意味着它是一个允许定制的特性。
在UsernamePasswordAuthenticationFilter中使用的AuthenticationDetailsSource是一个标准的Web认证源,携带的是用户的sessionId和IP地址。
有了HttpServletRequest之后,一切都将变得非常顺畅。基于图形验证码的场景,我们可以继承
WebAuthenticationDetails,并扩展需要的信息。
package com.by.security.provider;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpSession;import org.springframework.security.web.authentication.WebAuthenticationDetails;/** * @Author: Cyz * @Description: * @Date: create in 2024/6/4 16:07 */public class MyWebAuthenticationDetails extends WebAuthenticationDetails { private boolean imageCodeIsRight; public boolean isImageCodeIsRight() { return imageCodeIsRight; } public MyWebAuthenticationDetails(HttpServletRequest request) { super(request); String requestCode = request.getParameter("captcha"); HttpSession session = request.getSession(); String sessionCode = (String) session.getAttribute("captcha"); if(sessionCode!=null){ session.removeAttribute("captcha"); if(sessionCode.equals(requestCode)){ this.imageCodeIsRight = true; } } } public MyWebAuthenticationDetails(String remoteAddress, String sessionId) { super(remoteAddress, sessionId); }}
将它提供给一个自定义的AuthenticationDetailsSource。
package com.by.security.provider;import jakarta.servlet.http.HttpServletRequest;import org.springframework.security.web.authentication.WebAuthenticationDetails;import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;/** * @Author: Cyz * @Description: * @Date: create in 2024/6/4 16:08 */public class MyWebAuthenticationDetailsSource extends WebAuthenticationDetailsSource{ @Override public WebAuthenticationDetails buildDetails(HttpServletRequest context) { return new MyWebAuthenticationDetails(context); }}
接下来实现我们自定义的AuthenticationProvider。
package com.by.security.provider;import com.by.security.exception.VerificationCodeException;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;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.stereotype.Component;/** * @Author: Cyz * @Description: * @Date: create in 2024/6/4 16:06 */@Componentpublic class MyDaoAuthenticationProvider extends DaoAuthenticationProvider{ public MyDaoAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder){ super.setUserDetailsService(userDetailsService); super.setPasswordEncoder(passwordEncoder); } //在这里获取request 通过request来获取验证码 作比对 @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { MyWebAuthenticationDetails details = (MyWebAuthenticationDetails) authentication.getDetails(); if(!details.isImageCodeIsRight()){ throw new VerificationCodeException(); } super.additionalAuthenticationChecks(userDetails, authentication); }}
想要应用自定义的AuthenticationProvider 和AuthenticationDetailsSource,还需在WebSecurityConfig中完成剩余的配置。
http.formLogin(formLogin-> formLogin.authenticationDetailsSource(new MyWebAuthenticationDetailsSource()) .loginPage("/login").permitAll() //登录页面 .loginProcessingUrl("/login") //登录接口可以匿名访问 .defaultSuccessUrl("/index") //登录成功访问/index页面 );
参考个人博客:cyz
标签: #消息认证的实现过程包括