SpringSecurity-UsernamePasswordAuthenticationFilter的作用
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的作用的更多相关文章
- SpringSecurity-RememberMeAuthenticationFilter的作用
启用remember-me功能,在配置文件中的http节点下添加: <remember-me remember-me-parameter="remember-me" reme ...
- SpringSecurity在Springboot下使用的初步体验
SpringSecurity曾经在十年前非常火热,只要是做权限系统,当时几乎非用它不可,记得是在XML文件里一堆的配置.曾几何时,Shiro冒了出来,以其简洁和轻量的风格慢慢地捕获了众多码农的心,从此 ...
- SpringSecurity自定义UsernamePasswordAuthenticationFilter
UsernamePasswordAuthenticationFilter介绍 UsernamePasswordAuthenticationFilter是AbstractAuthenticationPr ...
- [转]springSecurity源码分析—DelegatingFilterProxy类的作用
使用过springSecurity的朋友都知道,首先需要在web.xml进行以下配置, <filter> <filter-name>springSecurityFilterC ...
- day5 SpringSecurity权限控制jsr250注解不起作用 AOP日志排除不需要织入的方法 web.xml配置错误码页面
- SpringSecurity个性化用户认证流程
⒈自定义登录页面 package cn.coreqi.security.config; import org.springframework.context.annotation.Bean; impo ...
- SpringSecurity的Filter执行顺序在源码中的体现
在网上看各种SpringSecurity教程时,都讲到了SpringSecurity的Filter顺序.但是一直不知道这个顺序在源码中是如何体现的.今天一步一步的查找,最终找到顺序是在FilterCo ...
- SpringSecurity学习之自定义过滤器
我们系统中的认证场景通常比较复杂,比如说用户被锁定无法登录,限制登录IP等.而SpringSecuriy最基本的是基于用户与密码的形式进行认证,由此可知它的一套验证规范根本无法满足业务需要,因此扩展势 ...
- springboot+mybatis+springSecurity+thymeleaf
配置步骤: .pom <dependencies> <dependency> <groupId>org.springframework.security</g ...
- CAS客户端与SpringSecurity集成
4. CAS客户端与SpringSecurity集成 4.1 Spring Security测试工程搭建 (1)建立Maven项目casclient_demo3 ,引入spring依赖和spring ...
随机推荐
- python自动化测试入门篇-postman
接口测试基础-postman 常用的接口有两种:webservice接口和http api接口. Webservice接口是走soap协议通过http传输,请求报文和返回报文都是xml格式. http ...
- [spring源码] 小白级别的源码解析IOC容器的依赖注入(三)
上一篇介绍了ioc容器的初始化过程,主要完成了ioc容器建立beanDefinition数据映射.并没有看到ioc容器对bean依赖关系进行注入. 接口getbean就是出发依赖注入发生的地方.下面从 ...
- 在git服务器上创建项目过程及遇到的问题
一: 登录git服务器,输入用户名,密码等 二: New Project 添加项目 设置组可见,项目名称等. 创建成功的项目可以看到该项目的clone地址,可以通过http,ssh两种方式来获取: 三 ...
- Vue-- 监听路由变化,数据无法更新?
之前写的Vue项目,有个问题困扰了好久.新闻板块有推荐.精华.最新等几个Tab,设想通过切换Tab,改变路由参数(get/news/:tab)去获取对应数据,然后渲染到页面(用的是同一套组件),问题来 ...
- 划分树(poj2104)
poj2104 题意:给出n个数,有m次查询,每次查询要你找出 l 到 r 中第 k 大的数: 思路:划分树模板题 上述图片展现了查询时如何往下递推的过程 其中ly表示 [sl,l) 中有多少个数进入 ...
- case语法
一.文件系统访问列表 FACL :Filesystem Access Control List 文件系统访问列表 利用文件扩展保存额外的访问控制权限. setfacl: -m:设定访问控制权限 ...
- python day18--面向对象,继承
# class Animal: # breath = '呼吸' # # def __init__(self, name, sex, age): # self.name = name # self.se ...
- Spring Data JPA 常用注解 @Query、@NamedQuery
1.@Transient @Transient表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性:如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则ORM框架 ...
- [LeetCode&Python] Problem 925. Long Pressed Name
Your friend is typing his name into a keyboard. Sometimes, when typing a character c, the key might ...
- python3 基础整理
基础语法 1.python中区分大小写 2.查看关键字用 import keyword print (keyword.kwlist) 3.注释 # 单行注释,多行注释的快捷键是ctr+/,取消注释的 ...