UsernamePasswordAuthenticationFilter应该是我们最关注的Filter,因为它实现了我们最常用的基于用户名和密码的认证逻辑。

先看一下一个常用的form-login配置:

 <form-login login-page="/login"
username-parameter="ssoId"
password-parameter="password"
authentication-failure-url ="/loginfailure"
default-target-url="/loginsuccess"/>
<logout invalidate-session="true"/>

在这里可以自定义表单中对应的用户名密码的name,已经登录登录成功或失败后跳转的url地址以及登录表单的action。

  UsernamePasswordAuthenticationFilter继承虚拟类AbstractAuthenticationProcessingFilter。

  AbstractAuthenticationProcessingFilter要求设置一个authenticationManager,authenticationManager的实现类将实际处理请求的认证。AbstractAuthenticationProcessingFilter将拦截符合过滤规则的request,并试图执行认证。子类必须实现 attemptAuthentication 方法,这个方法执行具体的认证。

  认证处理:如果认证成功,将会把返回的Authentication对象存放在SecurityContext;然后setAuthenticationSuccessHandler(AuthenticationSuccessHandler)

方法将会调用;这里处理认证成功后跳转url的逻辑;可以重新实现AuthenticationSuccessHandler的onAuthenticationSuccess方法,实现自己的逻辑,比如需要返回json格式数据时,就可以在这里重新相关逻辑。如果认证失败,默认会返回401代码给客户端,当然也可以在<form-login>节点中配置失败后跳转的url,还可以重写AuthenticationFailureHandler的onAuthenticationFailure方法实现自己的逻辑。

一个典型的自定义配置如下:

 <beans:bean id="restfulUsernamePasswordAuthenticationFilter"
class="com.kingdee.core.config.RestfulUsernamePasswordAuthenticationFilter">
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="authenticationSuccessHandler" ref="restfulAuthenticationSuccessHandler" />
<beans:property name="authenticationFailureHandler" ref="restfulAuthenticationFailureHandler" />
<beans:property name="loginUrl" value="/login/restful" />
</beans:bean>

下面先看一下authentication-manager的配置,这个配置实现自定义UserDetail,需要重新实现一个继承UserDetailsService接口的类。

 <authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="customUserDetailsService">
<password-encoder ref="bcryptEncoder"/>
</authentication-provider>
</authentication-manager>

我们看到authentication-manager节点有一个子节点authentication-provider,而authentication-provider有一个属性user-service-ref,user-service-ref的值就是我们要实现的自定义类。

整个调用过程大致如下:

  继承虚拟类AbstractAuthenticationProcessingFilter的UsernamePasswordAuthenticationFilter实现了attemptAuthentication方法

 public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
} String username = obtainUsername(request);
String password = obtainPassword(request); if (username == null) {
username = "";
} if (password == null) {
password = "";
} username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password); // Allow subclasses to set the "details" property
setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest);
}

这个方法的最后this.getAuthenticationManager().authenticate(authRequest)是实现自接口Authentication,而实现这个接口的类中有一个叫ProviderManager的,它有一个成员变量List<AuthenticationProvider>,对应于我们配置文件中的authentication-provider,这里也说明是可以配置多个authentication-provider的。我们只使用一个我们需要的。我们需要关注的是AbstractUserDetailsAuthenticationProvider这个虚拟类,它实现了我们所需要的authenticate方法:

 public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported")); // Determine username
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName(); boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username); if (user == null) {
cacheWasUsed = false; try {
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found"); if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
else {
throw notFound;
}
} Assert.notNull(user,
"retrieveUser returned null - a violation of the interface contract");
} try {
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException exception) {
if (cacheWasUsed) {
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
else {
throw exception;
}
} postAuthenticationChecks.check(user); if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
} Object principalToReturn = user; if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
} return createSuccessAuthentication(principalToReturn, authentication, user);
}

从代码中可以看到,它会先从cache中取user(这与配置有关,这里我们不涉及),如果没有,在执行retrieveUser方法。代码中还可以看到,UsernameNotFoundException默认是被转换成BadCredentialsException的。

它的子类DaoAuthenticationProvider重写了retrieveUser方法:

 protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
UserDetails loadedUser; try {
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
}
catch (UsernameNotFoundException notFound) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
passwordEncoder.isPasswordValid(userNotFoundEncodedPassword,
presentedPassword, null);
}
throw notFound;
}
catch (Exception repositoryProblem) {
throw new InternalAuthenticationServiceException(
repositoryProblem.getMessage(), repositoryProblem);
} if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}

在代码第7行可以看到,UserDetails从UserDetailsService().loadUserByUsername(username)中获得的。我们已经配置了userService方法,所以只要在配置类中重写loadUserByUsername(username)方法就可以了。这里需要注意的是我们重写的方法需要返回一个实现了UserDetails接口的对象,而org.springframework.security.core.userdetails.User就是我们经常实际返回的对象。

它的一个构造方法如下:

 public User(String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) { if (((username == null) || "".equals(username)) || (password == null)) {
throw new IllegalArgumentException(
"Cannot pass null or empty values to constructor");
} this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
}

我们根据自己的需要,从数据库中取得user和user对应的权限,构造一个org.springframework.security.core.userdetails.User返回即可。

这里只是重新实现了User的认证方法,如果想在SecurityContext中添加用户的其他信息,如email,address等,可以新指定一个authentication-provider的实现类,可以实现复用DaoAuthenticationProvider的大部分代码,只需要添加authentication.setDetails的相关代码即可。虽然UsernamePasswordAuthenticationFilter的注释是在setDetails(request, authRequest);方法中实现添加自定义的details,但也可以根据实际情况修改。甚至可以不用在这里修改,直接把需要的信息放在httpSession中。

这些博客都是重在理解SpringSecurity的工作过程,有助于重写一些自己的逻辑,但不涉及重写的具体实现(这些实现很多是可以在网上找到的)。

SpringSecurity-UsernamePasswordAuthenticationFilter的作用的更多相关文章

  1. SpringSecurity-RememberMeAuthenticationFilter的作用

    启用remember-me功能,在配置文件中的http节点下添加: <remember-me remember-me-parameter="remember-me" reme ...

  2. SpringSecurity在Springboot下使用的初步体验

    SpringSecurity曾经在十年前非常火热,只要是做权限系统,当时几乎非用它不可,记得是在XML文件里一堆的配置.曾几何时,Shiro冒了出来,以其简洁和轻量的风格慢慢地捕获了众多码农的心,从此 ...

  3. SpringSecurity自定义UsernamePasswordAuthenticationFilter

    UsernamePasswordAuthenticationFilter介绍 UsernamePasswordAuthenticationFilter是AbstractAuthenticationPr ...

  4. [转]springSecurity源码分析—DelegatingFilterProxy类的作用

    使用过springSecurity的朋友都知道,首先需要在web.xml进行以下配置, <filter>  <filter-name>springSecurityFilterC ...

  5. day5 SpringSecurity权限控制jsr250注解不起作用 AOP日志排除不需要织入的方法 web.xml配置错误码页面

  6. SpringSecurity个性化用户认证流程

    ⒈自定义登录页面 package cn.coreqi.security.config; import org.springframework.context.annotation.Bean; impo ...

  7. SpringSecurity的Filter执行顺序在源码中的体现

    在网上看各种SpringSecurity教程时,都讲到了SpringSecurity的Filter顺序.但是一直不知道这个顺序在源码中是如何体现的.今天一步一步的查找,最终找到顺序是在FilterCo ...

  8. SpringSecurity学习之自定义过滤器

    我们系统中的认证场景通常比较复杂,比如说用户被锁定无法登录,限制登录IP等.而SpringSecuriy最基本的是基于用户与密码的形式进行认证,由此可知它的一套验证规范根本无法满足业务需要,因此扩展势 ...

  9. springboot+mybatis+springSecurity+thymeleaf

    配置步骤: .pom <dependencies> <dependency> <groupId>org.springframework.security</g ...

  10. CAS客户端与SpringSecurity集成

    4. CAS客户端与SpringSecurity集成 4.1 Spring Security测试工程搭建 (1)建立Maven项目casclient_demo3 ,引入spring依赖和spring ...

随机推荐

  1. windows10安装MongoDB服务启动不了 😀✅已解决!

    WIndows的服务中,有一个服务需要以NT  AUTHORITY\NetworkService用户启动,但怎么也启动不起来,使用本地系统帐户启动没有任何问题,但是换成NetworkService就启 ...

  2. 用Apache Ant在Weka中嵌入新算法

    本文将介绍一种新的添加新的算法到Weka中的方法,国内的论坛基本都是通过IDE(Eclipse或NetBeans)编译,详细教程请见上一篇博客.经研究,发现国外的网站很流行用Ant这个方法,教程奉上. ...

  3. angular 多端打包

    1.在environments文件夹里新建三个文件: //生产环境 environment.prod.ts: export const environment = { production: true ...

  4. Android基础知识(一)

    前言 前言 从软件测试最终目的发现问题缺陷来看,Findyou比较认同一个观念,测试的能力大致可以划分成三个能力层次:发现问题.定位问题.预防问题.有机会探讨一下这个分类. 发现问题各种方式方法,比如 ...

  5. JS的防抖和节流

    数个月之前,在一次前端的性能优化中,接触到了JS中防抖和节流,一开始还不明白他们的应用在哪里,可后来才知道,这是前端中最基础的性能优化,在绑定 scroll .resize 这类事件时,当它发生时,它 ...

  6. HTML5⑥

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  7. CLR via C#--------CLR的执行模式

    CLR:是一个可由多种编程语言使用的“运行时”. CLR的核心功能(比如 内存管理.程序集加载.安全性.异常处理.线程同步)可由面向CLR的所有语言使用. CLR是完全围绕类型展开的. 面向CLR的语 ...

  8. NSIS脚本 打包安装程序

    相关工具 nsis http://nsis.sourceforge.net/Special_Builds HM NIS Edit http://hmne.sourceforge.net/ 例子: ; ...

  9. locate语法

    1.命令格式:locate [参数] [文件] 2.命令功能:locate命令可以在搜寻数据库时快速找到档案,数据库由updatedb程序来更新,updatedb是由cron daemon周期性建立的 ...

  10. TX锁处理

    实际处理后,在测试环境中模拟还原TX锁,及处理. 本篇博客目录: 1.TX锁模拟实际环境 2.登陆数据库,查询相关信息 3.确认锁源头,kill进程释放资源 一.TX锁模拟 sess_1 SQL> ...