spring-security使用-更友好的方式扩展登录AuthenticationProvider(三)
说明
在 spring-security使用-登录(一) 我们使用的是重写了Spring-security的filter的方式来进行自定义,但是这样的弊端,就是侵入太大。直接把spring-security的filter给替换掉了,
通过AuthenticationProvider的方式是在spring-security的filter内部留的扩展点进行扩展自定义登录逻辑
接口定义
AuthenticationProvider
public interface AuthenticationProvider { /** * 验证用户身份 * @param var1 * @return * @throws AuthenticationException */ Authentication authenticate(Authentication var1) throws AuthenticationException; /** * supports 则用来判断当前的 AuthenticationProvider 是否支持对应的 Authentication。 * @param var1 * @return */ boolean supports(Class<?> var1); }
Authentication
封装用户身份信息
public interface Authentication extends Principal, Serializable { //用来获取用户的权限。 Collection<? extends GrantedAuthority> getAuthorities(); //方法用来获取用户凭证,一般来说就是密码。 Object getCredentials(); //方法用来获取用户携带的详细信息,可能是当前请求之类的东西。 Object getDetails(); //方法用来获取当前用户,可能是一个用户名,也可能是一个用户对象。 Object getPrincipal(); //当前用户是否认证成功。 boolean isAuthenticated(); void setAuthenticated(boolean var1) throws IllegalArgumentException; }
类图
UsernamePasswordAuthenticationFilter 用的就是UserNamePasswordAuthenticationToken
使用AuthenticationProvider自定义登录逻辑
1.增加自定义provider继承DaoAuthenticationProvider
public class CodeAuthenticationProvider extends DaoAuthenticationProvider { @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) { HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String code = req.getParameter("code"); String verify_code = (String) req.getSession().getAttribute("verify_code"); if (code == null || verify_code == null || !code.equals(verify_code)) { throw new AuthenticationServiceException("验证码错误"); } super.additionalAuthenticationChecks(userDetails, authentication); } }
2.在public class SecurityConfig extends WebSecurityConfigurerAdapter 类增加以下
/** * 对密码进行加密的实例 * @return */ @Bean PasswordEncoder passwordEncoder() { /** * 不加密所以使用NoOpPasswordEncoder * 更多可以参考PasswordEncoder 的默认实现官方推荐使用: BCryptPasswordEncoder,BCryptPasswordEncoder */ return NoOpPasswordEncoder.getInstance(); } /** * 自定义provider * @return */ public CodeAuthenticationProvider codeAuthenticationProvider() { CodeAuthenticationProvider myAuthenticationProvider = new CodeAuthenticationProvider(); //设置passorderEncoder myAuthenticationProvider.setPasswordEncoder(passwordEncoder()); //设置UserDetailsService 可以参考第一篇登录使用例子自定义userDetailServices myAuthenticationProvider.setUserDetailsService(userService); return myAuthenticationProvider; } /** * 重写父类自定义AuthenticationManager 将provider注入进去 * 当然我们也可以考虑不重写 在父类的manager里面注入provider * @return * @throws Exception */ @Override protected AuthenticationManager authenticationManager() throws Exception { ProviderManager manager = new ProviderManager(Arrays.asList(codeAuthenticationProvider())); return manager; }
原理
spring-security使用-登录(一) 我们替换了默认的UsernamePasswordAuthenticationFilter
1.根据看这个类UsernamePasswordAuthenticationFilter我们可以看出他的父类实现了Servilet Fitler,所以spring-security是基于Filter的
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#doFilter 父类实现
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { //省略部分代码...... //调用子类的实现 也就是 UsernamePasswordAuthenticationFilter authResult = this.attemptAuthentication(request, response); if (authResult == null) { return; } } }
2.看子类实现
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#attemptAuthentication
public org.springframework.security.core.Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { //注意这里不支持post请求,如果我们要让支持post请求,就要重写filter吧这里去掉 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 封装 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); this.setDetails(request, authRequest); //委托给AuthenticationManager 执行 我们上面重写用的是ProviderManager return this.getAuthenticationManager().authenticate(authRequest); } }
3.providerManger实现
org.springframework.security.authentication.ProviderManager#authenticate
public org.springframework.security.core.Authentication authenticate(org.springframework.security.core.Authentication authentication) throws AuthenticationException { //获得Authentication的类型 Class<? extends org.springframework.security.core.Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; AuthenticationException parentException = null; org.springframework.security.core.Authentication result = null; org.springframework.security.core.Authentication parentResult = null; boolean debug = logger.isDebugEnabled(); //获得所有的Providers 就是我们定义的CodeAuthenticationProvider Iterator var8 = this.getProviders().iterator(); while(var8.hasNext()) { //迭代器迭代获取 AuthenticationProvider provider = (AuthenticationProvider)var8.next(); //判断是否能处理 if (provider.supports(toTest)) { if (debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { //调用provider的authenticate 执行身份认证 result = provider.authenticate(authentication); if (result != null) { this.copyDetails(authentication, result); break; } } catch (InternalAuthenticationServiceException | AccountStatusException var13) { this.prepareException(var13, authentication); throw var13; } catch (AuthenticationException var14) { lastException = var14; } } } //省略部分代码....... }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
2019-01-04 git基本概念(一)
2019-01-04 mac Gitblit安装
2019-01-04 java陷阱之spring事物管理导致锁无效