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. 百度OCR

    注意点:  图像数据, base64编码后,记得要urlencode, 否则会提示image format wrong winform 多窗体相互切换form public partial class ...

  2. python之模块定义、导入、优化详解

    一.模块 1.模块的定义 模块是一组包含了一组功能的python文件,比如test.py,模块名为test,可以通过import test进行调用.模块可以分为以下四个通用类别 1 使用python编 ...

  3. Python *Mix_w

    Python的历史起源: 1989年圣诞节期间,由吉多.范罗苏姆创始. Python的优点"优美""明确""简单" python是一门解释型 ...

  4. Java易错题(1)

    检查程序,是否存在问题,如果存在指出问题所在,如果不存在,说明输出结果. public class HelloB extends HelloA { public HelloB() { } { Syst ...

  5. 第一节课:java介绍以及java开发环境的安装

    互联网时代 什么是软件 什么是硬件 什么是操作系统 java是什么? 计算机高级程序语言 java能做什么? 开发软件(网站.app.游戏.应用系统.嵌入式) java对应的职位链(需求->产品 ...

  6. Java语法基础学习DayFourteen(IO)

    一.java.io.FIle类 1.特点 (1)凡是与输入.输出相关的类.接口等都定义在java.io包下. (2)File是一个类,使用构造器创建对象,此对象对应一个文件(.txt .avi .do ...

  7. elasticsearch 的查询 /_nodes/stats 各字段意思

    /_nodes/stats 字段意思   “”   1 {  2  "_nodes": {3 "total": 1, "successful" ...

  8. 获取当前时间并格式化,CTime类

    CTime类,此类应该不是C++标准类库,属于windows封装的关于时间的类库,使用环境应该为 Win32程序,MFC程序,VC++程序 CTime tm = CTime::GetCurrentTi ...

  9. python 环境下 安装 gdal

    起因:需要做一个城市扩张的东西,然后再GitHub上下载了一段代码,不过作者没怎么说清楚要怎么用,早上琢磨半天,归结到我需要先下载python的gdal模块. 关于:搜索下来,发现gdal(Geosp ...

  10. 使用vue+koa实现一个简单的图书小程序(1)

    这个系列的博客用来记录我开发时候遇到的问题以及学习到的知识 边做边学: 前后端分离,高内聚低耦合小程序端使用了mpvue 内部使用了vuejs的语法 来做整个小程序的渲染层 后端使用的是koa2搭建一 ...