2.SpringSecurity源码的初探
创始人
2024-05-28 15:57:07
0

SpringSecurity源码的初探

image.png

一、SpringSecurity中的核心组件

  在SpringSecurity中的jar分为4个,作用分别为

jar作用
spring-security-coreSpringSecurity的核心jar包,认证和授权的核心代码都在这里面
spring-security-config如果使用Spring Security XML名称空间进行配置或Spring Security的
Java configuration支持,则需要它
spring-security-web用于Spring Security web身份验证服务和基于url的访问控制
spring-security-test测试单元

1.SecurityContextHolder

  首先来看看在spring-security-core中的SecurityContextHolder,这个是一个非常基础的对象,存储了当前应用的上下文SecurityContext,而在SecurityContext可以获取Authentication对象。也就是当前认证的相关信息会存储在Authentication对象中。

image.png

  默认情况下,SecurityContextHolder是通过 ThreadLocal来存储对应的信息的。也就是在一个线程中我们可以通过这种方式来获取当前登录的用户的相关信息。而在SecurityContext中就只提供了对Authentication对象操作的方法

public interface SecurityContext extends Serializable {Authentication getAuthentication();void setAuthentication(Authentication authentication);}

xxxStrategy的各种实现

image.png

策略实现说明
GlobalSecurityContextHolderStrategy把SecurityContext存储为static变量
InheritableThreadLocalSecurityContextStrategy把SecurityContext存储在InheritableThreadLocal中
InheritableThreadLocal解决父线程生成的变量传递到子线程中进行使用
ThreadLocalSecurityContextStrategy把SecurityContext存储在ThreadLocal中

2.Authentication

  Authentication是一个认证对象。在Authentication接口中声明了如下的相关方法。

public interface Authentication extends Principal, Serializable {// 获取认证用户拥有的对应的权限Collection getAuthorities();// 获取哦凭证Object getCredentials();// 存储有关身份验证请求的其他详细信息。这些可能是 IP地址、证书编号等Object getDetails();// 获取用户信息 通常是 UserDetails 对象Object getPrincipal();// 是否认证boolean isAuthenticated();// 设置认证状态void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;}

image.png

  基于上面讲解的三者的关系我们在项目中如此来获取当前登录的用户信息了。

    public String hello(){Authentication authentication = SecurityContextHolder.getContext().getAuthentication();Object principal = authentication.getPrincipal();if(principal instanceof UserDetails){UserDetails userDetails = (UserDetails) principal;System.out.println(userDetails.getUsername());return "当前登录的账号是:" + userDetails.getUsername();}return "当前登录的账号-->" + principal.toString();}

  调用 getContext()返回的对象是 SecurityContext接口的一个实例,这个对象就是保存在线程中的。接下来将看到,Spring Security中的认证大都返回一个 UserDetails的实例作为principa。

3.UserDetailsService

  在上面的关系中我们看到在Authentication中存储当前登录用户的是Principal对象,而通常情况下Principal对象可以转换为UserDetails对象。UserDetails是Spring Security中的一个核心接口。它表示一个principal,但是是可扩展的、特定于应用的。可以认为 UserDetails是数据库中用户表记录和Spring Security在 SecurityContextHolder中所必须信息的适配器。

public interface UserDetails extends Serializable {// 对应的权限Collection getAuthorities();// 密码String getPassword();// 账号String getUsername();// 账号是否过期boolean isAccountNonExpired();// 是否锁定boolean isAccountNonLocked();// 凭证是否过期boolean isCredentialsNonExpired();// 账号是否可用boolean isEnabled();}

  而这个接口的默认实现就是 User

image.png

  那么这个UserDetails对象什么时候提供呢?其实在我们前面介绍的数据库认证的Service中我们就用到了,有一个特殊接口 UserDetailsService,在这个接口中定义了一个loadUserByUsername的方法,接收一个用户名,来实现根据账号的查询操作,返回的是一个 UserDetails对象。

public interface UserDetailsService {UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;}

  UserDetailsService接口的实现有如下:

image.png

  Spring Security提供了许多 UserDetailsSerivice接口的实现,包括使用内存中map的实现(InMemoryDaoImpl 低版本 InMemoryUserDetailsManager)和使用JDBC的实现(JdbcDaoImpl)。但在实际开发中我们更喜欢自己来编写,比如UserServiceImpl我们的案例

/*** 用户的Service*/
public interface UserService extends UserDetailsService {}/*** UserService接口的实现类*/
@Service
public class UserServiceImpl implements UserService {@AutowiredUserMapper userMapper;/*** 根据账号密码验证的方法* @param username* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SysUser user = userMapper.queryByUserName(username);System.out.println("---------->"+user);if(user != null){// 账号对应的权限List authorities = new ArrayList<>();authorities.add(new SimpleGrantedAuthority("ROLE_USER"));// 说明账号存在 {noop} 非加密的使用UserDetails details = new User(user.getUserName(),user.getPassword(),true,true,true,true,authorities);return details;}throw new UsernameNotFoundException("账号不存在...");}
}

4.GrantedAuthority

  我们在Authentication中看到不光关联了Principal还提供了一个getAuthorities()方法来获取对应的GrantedAuthority对象数组。和权限相关,后面在权限模块详细讲解

public interface GrantedAuthority extends Serializable {String getAuthority();}

上面介绍到的核心对象小结:

核心对象作用
SecurityContextHolder用于获取SecurityContext
SecurityContext存放了Authentication和特定于请求的安全信息
Authentication特定于Spring Security的principal
GrantedAuthority对某个principal的应用范围内的授权许可
UserDetail提供从应用程序的DAO或其他安全数据源构建Authentication对象所需的信息
UserDetailsService接受String类型的用户名,创建并返回UserDetail

有了这块的基础我们可以来看看认证的实现流程了

二、认证流程

  接下来我们直接来看看SpringSecurity中是如何处理认证操作的。

  • 1.账号验证
  • 2.密码验证
  • 3.记住我–>cookie信息
  • 4.登录成功–>跳转

1.UsernamePasswordAuthenticationFilter

  在SpringSecurity中处理认证逻辑是在UsernamePasswordAuthenticationFilter这个过滤器中实现的。至于这个过滤器是怎么执行的,我们后面会详细的讲解,UsernamePasswordAuthenticationFilter继承于AbstractAuthenticationProcessingFilter这个父类。

image.png

  而在UsernamePasswordAuthenticationFilter没有实现doFilter方法,所以认证的逻辑需要先看AbstractAuthenticationProcessingFilter中的doFilter方法。

image.png

上面的核心代码是

Authentication authenticationResult = attemptAuthentication(request, response);

attemptAuthentication方法的作用是获取Authentication对象其实就是对应的认证过程,我们进入到UsernamePasswordAuthenticationFilter中来查看具体的实现。

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

上面代码的含义非常清晰

  1. 该方法只支持POST方式提交的请求
  2. 获取账号和密码
  3. 通过账号密码获取了UsernamePasswordAuthenticationToken对象
  4. 设置请求的详细信息
  5. 通过AuthenticationManager来完成认证操作

在上面的逻辑中出现了一个对象AuthenticationManager

2.AuthenticationManager

  AuthenticationManager接口中就定义了一个方法authenticate方法,处理认证的请求。

public interface AuthenticationManager {Authentication authenticate(Authentication authentication) throws AuthenticationException;}

  在这里AuthenticationManager的默认实现是ProviderManager.而在ProviderManager的authenticate方法中实现的操作是循环遍历成员变量List providers。该providers中如果有一个AuthenticationProvider的supports函数返回true,那么就会调用该AuthenticationProvider的authenticate函数认证,如果认证成功则整个认证过程结束。如果不成功,则继续使用下一个合适的AuthenticationProvider进行认证,只要有一个认证成功则为认证成功。

image.png

在当前环境下默认的实现提供是

image.png

进入到AbstractUserDetailsAuthenticationProvider中的认证方法

image.png

然后进入到retrieveUser方法中,具体的实现是DaoAuthenticationProvider

	@Overrideprotected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {prepareTimingAttackProtection();try {// getUserDetailsService会获取到我们自定义的UserServiceImpl对象,也就是会走我们自定义的认证方法了UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);if (loadedUser == null) {throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");}return loadedUser;}catch (UsernameNotFoundException ex) {mitigateAgainstTimingAttack(authentication);throw ex;}catch (InternalAuthenticationServiceException ex) {throw ex;}catch (Exception ex) {throw new InternalAuthenticationServiceException(ex.getMessage(), ex);}}

  如果账号存在就会开始密码的验证,不过在密码验证前还是会完成一个检查

image.png

image.png

然后就是具体的密码验证

additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);

具体的验证的逻辑

	protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {// 密码为空if (authentication.getCredentials() == null) {this.logger.debug("Failed to authenticate since no credentials provided");throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}// 获取表单提交的密码String presentedPassword = authentication.getCredentials().toString();// 表单提交的密码和数据库查询的密码 比较是否相对if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {this.logger.debug("Failed to authenticate since password does not match stored value");throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}}

上面的逻辑会通过对应的密码编码器来处理,如果是非加密的情况会通过NoOpPasswordEncoder来处理

    public boolean matches(CharSequence rawPassword, String encodedPassword) {return rawPassword.toString().equals(encodedPassword);}

image.png

如果有加密处理,就选择对应的加密对象来处理,比如我们上面使用的BCryptPasswordEncoder来处理

image.png

相关内容

热门资讯

北京国安联赛保持不败 【#北京国安联赛保持不败#】#北京国安跨赛季19场不败#5月5日,北京国安队在2025赛季中超联赛第...
特锐德(300001.SZ)及... 格隆汇5月5日丨特锐德(300001.SZ)公布,近日,国家电网有限公司在其电子商务平台(https...
【漫画】“盲”果 转自:北京日报客户端近日,有游客反映“在三亚购买水果九斤变成六斤”。当地相关部门发布通报称,情况属实...
海南机场拟23.39亿元收购美... 本报讯 (记者李雯珊)5月1日,海南机场设施股份有限公司(以下简称“海南机场”)发布《关于收购海南美...
“五一”上海楼市掀起“改善潮”... 格隆汇5月5日|据一财,五一假期前夕,上海新房市场新增11个商品房项目过会,共计1577套房源于近日...
“五一”假期,郑州机场服务超4... 5日,记者从郑州机场了解到,今年五一假期,旅客乘机出行热情高涨,郑州机场迎来客流高峰。初步统计,在为...
离岸人民币兑美元升破7.2,创... 自触及7.4低点后,离岸人民币兑美元汇率已不断走强,4月8日至今上涨近2.7%文|康恺编辑|张威随着...
“川妹子”陈佳夺跳水世界杯冠军... 转自:四川在线刘晓丹 陈凡逸 四川在线记者 行晓艺5月4日,在北京举行的2025年世界泳联跳水世界杯...
让舞台成为历史与青春的共鸣场 ... 转自:扬子晚报灯光渐亮,舞台苏醒,一场属于青春少年的戏剧盛典,在聚光灯下热烈开演。在这里,每句台词都...
C视频丨抓住小长假尾巴 成都... 四川在线记者 吴聃5月5日是“五一”小长假最后一天,不少成都市民选择城市近郊休闲,走进公园与家人、朋...
“五一”车市冷暖实探:五一假期... 【“五一”车市冷暖实探:#五一假期上海车展仍水泄不通# #五一假期试驾量明显提升#】“今年‘五一’我...
富创精密:大股东沈阳先进拟以1... 上证报中国证券网讯(记者骆民)富创精密公告,公司大股东沈阳先进制造技术产业有限公司(以下简称“沈阳先...
五一空中运动火爆高空跳伞滑翔伞... 来源:@央视财经微博 【#五一空中运动火爆##高空跳伞滑...
第二个“泡泡玛特”要来了 转自:宁波晚报近日,泛娱乐玩具生产商卡游时隔一年再次更新了招股书,冲击港股上市。相较于一年前因盲盒抽...
药石科技:股东吴希罕拟减持约7... 药石科技(SZ 300725,收盘价:32.16元)5月5日晚间发布公告称,持有公司股份约74.38...
中国黄金获颁“联合推广零售商”...   近日,在深圳举办的"2025年硬足金饰品联合推广项目启动会"上,中国黄金集团黄金珠宝股份有限公司...
洋河股份年报解析以慢即是快的哲... 来源:每日财报洋河股份领先的储酒能力和持续的技术创新投入,通过稳健的股东回报政策,为未来的持续增长提...
旅行者星际卫士全球首发 捷途汽... 本报记者 郭阳琛 石英婧 上海报道(捷途汽车全系旅行越野新品成团亮相“2025上海车展”。受访者/图...
刚刚通知:临时取消!宁波人别跑... 转自:宁波晚报今天下午,慈城古县城景区发布了关于“慈城古县城无人机表演因天气原因临时取消”的通知。慈...
慧博云通:筹划发行股份及支付现... 上证报中国证券网讯(记者骆民)慧博云通公告,公司正在筹划发行股份及支付现金购买资产并募集配套资金事项...