首页 > 其他分享 >转载:核心类

转载:核心类

时间:2023-06-08 20:03:58浏览次数:34  
标签:username 核心 认证 Authentication Spring Security 转载 UserDetails


1.1     Authentication

是一个接口,用来表示用户认证信息的,在用户登录认证之前相关信息会封装为一个Authentication具体实现类的对象,在登录认证成功之后又会生成一个信息更全面,包含用户权限等信息的Authentication对象,然后把它保存在SecurityContextHolder所持有的SecurityContext中,供后续的程序进行调用,如访问权限的鉴定等。

 

1.2     SecurityContextHolder

是用来保存SecurityContext的。SecurityContext中含有当前正在访问系统的用户的详细信息。默认情况下,SecurityContextHolder将使用ThreadLocal来保存SecurityContext,这也就意味着在处于同一线程中的方法中我们可以从ThreadLocal中获取到当前的SecurityContext。因为线程池的原因,如果我们每次在请求完成后都将ThreadLocal进行清除的话,那么我们把SecurityContext存放在ThreadLocal中还是比较安全的。这些工作spring Security已经自动为我们做了,即在每一次request结束后都将清除当前线程的ThreadLocal。

中定义了一系列的静态方法,而这些静态方法内部逻辑基本上都是通过SecurityContextHolder持有的SecurityContextHolderStrategy来实现的,如getContext()、setContext()、clearContext()等。而默认使用的strategy就是基于ThreadLocal的ThreadLocalSecurityContextHolderStrategy。另外,Spring Security还提供了两种类型的strategy实现,GlobalSecurityContextHolderStrategy和InheritableThreadLocalSecurityContextHolderStrategy,前者表示全局使用同一个SecurityContext,如C/S结构的客户端;后者使用InheritableThreadLocal来存放SecurityContext,即子线程可以使用父线程中存放的变量。

一般而言,我们使用默认的strategy就可以了,但是如果要改变默认的strategy,Spring Security为我们提供了两种方法,这两种方式都是通过改变strategyName来实现的。SecurityContextHolder中为三种不同类型的strategy分别命名为MODE_THREADLOCAL、MODE_INHERITABLETHREADLOCAL和MODE_GLOBAL。第一种方式是通过SecurityContextHolder的静态方法setStrategyName()来指定需要使用的strategy;第二种方式是通过系统属性进行指定,其中属性名默认为“spring.security.strategy”,属性值为对应strategy的名称。

使用一个Authentication对象来描述当前用户的相关信息。SecurityContextHolder中持有的是当前用户的SecurityContext,而SecurityContext持有的是代表当前用户相关信息的Authentication的引用。这个Authentication对象不需要我们自己去创建,在与系统交互的过程中,Spring Security会自动为我们创建相应的Authentication对象,然后赋值给当前的SecurityContext。但是往往我们需要在程序中获取当前用户的相关信息,比如最常见的是获取当前登录用户的用户名。在程序的任何地方,通过如下方式我们可以获取到当前用户的用户名。

public String getCurrentUsername() {
      Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
      if (principal instanceof UserDetails) {
         return ((UserDetails) principal).getUsername();
      }
      if (principal instanceof Principal) {
         return ((Principal) principal).getName();
      }
      return String.valueOf(principal);
   }

 

通过Authentication.getPrincipal()可以获取到代表当前用户的信息,这个对象通常是UserDetails的实例。获取当前用户的用户名是一种比较常见的需求,关于上述代码其实Spring Security在Authentication中的实现类中已经为我们做了相关实现,所以获取当前用户的用户名最简单的方式应当如下。

public String getCurrentUsername() {
      return SecurityContextHolder.getContext().getAuthentication().getName();
   }

此外,调用SecurityContextHolder.getContext()获取SecurityContext时,如果对应的SecurityContext不存在,则Spring Security将为我们建立一个空的SecurityContext并进行返回。

 

1.3     AuthenticationManager和AuthenticationProvider

是一个用来处理认证(Authentication)请求的接口。在其中只定义了一个方法authenticate(),该方法只接收一个代表认证请求的Authentication对象作为参数,如果认证成功,则会返回一个封装了当前用户权限等信息的Authentication对象进行返回。

Authentication authenticate(Authentication authentication) throws AuthenticationException;

 

在Spring Security中,AuthenticationManager的默认实现是ProviderManager,而且它不直接自己处理认证请求,而是委托给其所配置的AuthenticationProvider列表,然后会依次使用每一个AuthenticationProvider进行认证,如果有一个AuthenticationProvider认证后的结果不为null,则表示该AuthenticationProvider已经认证成功,之后的AuthenticationProvider将不再继续认证。然后直接以该AuthenticationProvider的认证结果作为ProviderManager的认证结果。如果所有的AuthenticationProvider的认证结果都为null,则表示认证失败,将抛出一个ProviderNotFoundException。校验认证请求最常用的方法是根据请求的用户名加载对应的UserDetails,然后比对UserDetails的密码与认证请求的密码是否一致,一致则表示认证通过。Spring Security内部的DaoAuthenticationProvider就是使用的这种方式。其内部使用UserDetailsService来负责加载UserDetails,UserDetailsService将在下节讲解。在认证成功以后会使用加载的UserDetails来封装要返回的Authentication对象,加载的UserDetails对象是包含用户权限等信息的。认证成功返回的Authentication对象将会保存在当前的SecurityContext中。

当我们在使用NameSpace时, authentication-manager元素的使用会使Spring Security 在内部创建一个ProviderManager,然后可以通过authentication-provider元素往其中添加AuthenticationProvider。当定义authentication-provider元素时,如果没有通过ref属性指定关联哪个AuthenticationProvider,Spring Security默认就会使用DaoAuthenticationProvider。使用了NameSpace后我们就不要再声明ProviderManager了。

<security:authentication-manageralias="authenticationManager">
      <security:authentication-provider
user-service-ref="userDetailsService"/>
   </security:authentication-manager>

如果我们没有使用NameSpace,那么我们就应该在ApplicationContext中声明一个ProviderManager。

 

1.3.1    认证成功后清除凭证

默认情况下,在认证成功后ProviderManager将清除返回的Authentication中的凭证信息,如密码。所以如果你在无状态的应用中将返回的Authentication信息缓存起来了,那么以后你再利用缓存的信息去认证将会失败,因为它已经不存在密码这样的凭证信息了。所以在使用缓存的时候你应该考虑到这个问题。一种解决办法是设置ProviderManager的eraseCredentialsAfterAuthentication 属性为false,或者想办法在缓存时将凭证信息一起缓存。

 

1.4     UserDetailsService

通过Authentication.getPrincipal()的返回类型是Object,但很多情况下其返回的其实是一个UserDetails的实例。UserDetails是Spring Security中一个核心的接口。其中定义了一些可以获取用户名、密码、权限等与认证相关的信息的方法。Spring Security内部使用的UserDetails实现类大都是内置的User类,我们如果要使用UserDetails时也可以直接使用该类。在Spring Security内部很多地方需要使用用户信息的时候基本上都是使用的UserDetails,比如在登录认证的时候。登录认证的时候Spring Security会通过UserDetailsService的loadUserByUsername()方法获取对应的UserDetails进行认证,认证通过后会将该UserDetails赋给认证通过的Authentication的principal,然后再把该Authentication存入到SecurityContext中。之后如果需要使用用户信息的时候就是通过SecurityContextHolder获取存放在SecurityContext中的Authentication的principal。

通常我们需要在应用中获取当前用户的其它信息,如Email、电话等。这时存放在Authentication的principal中只包含有认证相关信息的UserDetails对象可能就不能满足我们的要求了。这时我们可以实现自己的UserDetails,在该实现类中我们可以定义一些获取用户其它信息的方法,这样将来我们就可以直接从当前SecurityContext的Authentication的principal中获取这些信息了。上文已经提到了UserDetails是通过UserDetailsService的loadUserByUsername()方法进行加载的。UserDetailsService也是一个接口,我们也需要实现自己的UserDetailsService来加载我们自定义的UserDetails信息。然后把它指定给AuthenticationProvider即可。如下是一个配置UserDetailsService的示例。

<!-- 用于认证的AuthenticationManager -->
   <security:authentication-manageralias="authenticationManager">
      <security:authentication-provider
user-service-ref="userDetailsService" />
   </security:authentication-manager>
 
   <beanid="userDetailsService"
class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
      <propertyname="dataSource" ref="dataSource" />
   </bean>

 

上述代码中我们使用的JdbcDaoImpl是Spring Security为我们提供的UserDetailsService的实现,另外Spring Security还为我们提供了UserDetailsService另外一个实现,InMemoryDaoImpl。

 

 

其作用是从数据库中加载UserDetails信息。其中已经定义好了加载相关信息的默认脚本,这些脚本也可以通过JdbcDaoImpl的相关属性进行指定。关于JdbcDaoImpl使用方式会在讲解AuthenticationProvider的时候做一个相对详细一点的介绍。

1.4.1    JdbcDaoImpl

允许我们从数据库来加载UserDetails,其底层使用的是Spring的JdbcTemplate进行操作,所以我们需要给其指定一个数据源。此外,我们需要通过usersByUsernameQuery属性指定通过username查询用户信息的SQL语句;通过authoritiesByUsernameQuery属性指定通过username查询用户所拥有的权限的SQL语句;如果我们通过设置JdbcDaoImpl的enableGroups为true启用了用户组权限的支持,则我们还需要通过groupAuthoritiesByUsernameQuery属性指定根据username查询用户组权限的SQL语句。当这些信息都没有指定时,将使用默认的SQL语句,默认的SQL语句如下所示。

select username, password, enabled from users where username=? --根据username查询用户信息

select username, authority from authorities where username=? --根据username查询用户权限信息

select g.id, g.group_name, ga.authority from groups g, groups_members gm, groups_authorities ga where gm.username=? and g.id=ga.group_id and g.id=gm.group_id --根据username查询用户组权限

 

使用默认的SQL语句进行查询时意味着我们对应的数据库中应该有对应的表和表结构,Spring Security为我们提供的默认表的创建脚本如下。

create table users(
      username varchar_ignorecase(50) not null primary key,
      password varchar_ignorecase(50) not null,
      enabled boolean not null);
 
create table authorities (
      username varchar_ignorecase(50) not null,
      authority varchar_ignorecase(50) not null,
      constraint fk_authorities_users foreign key(username) references users(username));
      create unique index ix_auth_username on authorities (username,authority);
      
create table groups (
  id bigint generated by default as identity(start with 0) primary key,
  group_name varchar_ignorecase(50) notnull);
 
create table group_authorities (
  group_id bigint notnull,
  authority varchar(50) notnull,
  constraint fk_group_authorities_group foreign key(group_id) references groups(id));
 
create table group_members (
  id bigint generated by default as identity(start with 0) primary key,
  username varchar(50) notnull,
  group_id bigint notnull,
  constraint fk_group_members_group foreign key(group_id) references groups(id));
 
此外,使用jdbc-user-service元素时在底层Spring Security默认使用的就是JdbcDaoImpl。
   <security:authentication-manageralias="authenticationManager">
      <security:authentication-provider>
         <!-- 基于Jdbc的UserDetailsService实现,JdbcDaoImpl -->
         <security:jdbc-user-servicedata-source-ref="dataSource"/>
      </security:authentication-provider>
   </security:authentication-manager>

1.4.2    InMemoryDaoImpl

主要是测试用的,其只是简单的将用户信息保存在内存中。使用NameSpace时,使用user-service元素Spring Security底层使用的UserDetailsService就是InMemoryDaoImpl。此时,我们可以简单的使用user元素来定义一个UserDetails。

<security:user-service>
      <security:username="user" password="user" authorities="ROLE_USER"/>
   </security:user-service>

 

如上配置表示我们定义了一个用户user,其对应的密码为user,拥有ROLE_USER的权限。此外,user-service还支持通过properties文件来指定用户信息,如:

<security:user-serviceproperties="/WEB-INF/config/users.properties"/>
其中属性文件应遵循如下格式:
username=password,grantedAuthority[,grantedAuthority][,enabled|disabled]
所以,对应上面的配置文件,我们的users.properties文件的内容应该如下所示:
#username=password,grantedAuthority[,grantedAuthority][,enabled|disabled]
user=user,ROLE_USER

 

1.5     GrantedAuthority

的getAuthorities()可以返回当前Authentication对象拥有的权限,即当前用户拥有的权限。其返回值是一个GrantedAuthority类型的数组,每一个GrantedAuthority对象代表赋予给当前用户的一种权限。GrantedAuthority是一个接口,其通常是通过UserDetailsService进行加载,然后赋予给UserDetails的。

中只定义了一个getAuthority()方法,该方法返回一个字符串,表示对应权限的字符串表示,如果对应权限不能用字符串表示,则应当返回null。

针对GrantedAuthority有一个简单实现SimpleGrantedAuthority。该类只是简单的接收一个表示权限的字符串。Spring Security内部的所有AuthenticationProvider都是使用SimpleGrantedAuthority来封装Authentication对象。

标签:username,核心,认证,Authentication,Spring,Security,转载,UserDetails
From: https://blog.51cto.com/u_16131764/6442730

相关文章

  • 转载:基于表达式的权限控制
          springSecurity允许我们在定义URL访问或方法访问所应有的权限时使用SpringEL表达式,在定义所需的访问权限时如果对应的表达式返回结果为true则表示拥有对应的权限,反之则无。SpringSecurity可用表达式对象的基类是SecurityExpressionRoot,其为我们提供了如下在使用Spr......
  • 【转载】xsd文件验证xml的java实现
    importjava.io.File;importjava.io.IOException;importjavax.xml.transform.Source;importjavax.xml.transform.stream.StreamSource;importjavax.xml.validation.Schema;importjavax.xml.validation.SchemaFactory;importjavax.xml.validation.Validator;i......
  • [ASP.NET Core] 请求大小限制(转载)
    请求大小一般在文件上传的时候会用到,当然也防止传过来的参数过大情况。一、设置请求体的最大值如果不设置请求体大小默认是 30_000_000bytes,大约28.6MB,当超出大小时会出现如下错误:错误:Failedtoreadtherequestform.Requestbodytoolarge.Themaxrequestbodysize......
  • IDEA格式化代码不格式化注释-转载
     1、IDE如何在格式化代码的时候禁止格式化注释1.1、格式化前: 1.2、格式化后: 这里我们可以看到,在进行代码格式化的时候idea自动将我们的注释也进行了格式化,而且改变了注释的顺序。1.3、解决方案:  ......
  • 互联网的边缘与核心部分
    边缘部分是用户直接使用,用来进行通信和资源共享。核心部分由大量网络和连接网络的路由器组成。这部分是为边缘部分提供服务的。边缘部分处在互联网边缘的部分就是连接在互联网上的所有的主机。这些主机又称为端系统(endsystem)。主机间的通信其实是进程间的通信。在网络边缘的......
  • 2.ES核心
    3.ES核心3.1.核心概念3.1.1.索引一个索引就是一个拥有几分相似特征的文档的集合。一个索引由一个名字来标识(必须全部是小写字母),并且当我们要对这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。在一个集群中,可以定义任意多的索引。能搜索的数据必须索引......
  • Spring核心接口之InitializingBean
    一、InitializingBean接口说明InitializingBean接口为bean提供了属性初始化后的处理方法,它只包括afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。packageorg.springframework.beans.factory;/***Interfacetobeimplementedbybeanstha......
  • 【转载】configure: error: C compiler cannot create executables 错误解析
    1原文地址configure:error:Ccompilercannotcreateexecutables错误解析-to_be_better_wen-https://blog.csdn.net/to_be_better_wen/article/details/1306507742前言在编译开源软件的时候,有时会遇到"configure:error:Ccompilercannotcreateexecutables"的错......
  • XY610 4G 核心板—基于紫光展锐T610(虎贲T610)平台
        XY610 4G 核心板新一代LTE移动芯片平台,采用沉金生产工艺,耐腐蚀抗干扰,支持-20℃-70℃环境下7x24小时稳定运行,尺寸仅为40mmx50mmx2.8mm,可嵌入到各种智能产品中,助力智能产品便携化及功能差异化。应用旗舰级DynamlQ架构12nm制程工艺,采用2*Cortex-A75+6*Cortex-A55......
  • 网络学习笔记:华为 voice vlan配置(设备型号s7700核心)
    组网结构简介1、核心s7700下面连接电话交换机s5735(POE供电给IP话机),dhcp,voicevlan等配置在s7700实现,s5735充当傻瓜式交换机使用。2、目标实现数据vlan125给PC,语音vlan124给话机,话机连接交换机电口,PC连接话机的PC口使用。配置dhcp配置Vlan124/125#全局dhcpenable##......