`
jaesonchen
  • 浏览: 313394 次
  • 来自: ...
社区版块
存档分类
最新评论

转载:核心类

 
阅读更多

原文地址:http://haohaoxuexi.iteye.com/blog/2155786

 

1.1     Authentication

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

 

1.2     SecurityContextHolder

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

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

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

       Spring Security使用一个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 SecurityAuthentication中的实现类中已经为我们做了相关实现,所以获取当前用户的用户名最简单的方式应当如下。

   public String getCurrentUsername() {

      return SecurityContextHolder.getContext().getAuthentication().getName();

   }

 

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

 

1.3     AuthenticationManagerAuthenticationProvider

       AuthenticationManager是一个用来处理认证(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来负责加载UserDetailsUserDetailsService将在下节讲解。在认证成功以后会使用加载的UserDetails来封装要返回的Authentication对象,加载的UserDetails对象是包含用户权限等信息的。认证成功返回的Authentication对象将会保存在当前的SecurityContext中。

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

   <security:authentication-manager alias="authenticationManager">

      <security:authentication-provider

         user-service-ref="userDetailsService"/>

   </security:authentication-manager>

 

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

 

1.3.1    认证成功后清除凭证

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

 

1.4     UserDetailsService

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

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

   <!-- 用于认证的AuthenticationManager -->

   <security:authentication-manager alias="authenticationManager">

      <security:authentication-provider

         user-service-ref="userDetailsService" />

   </security:authentication-manager>

 

   <bean id="userDetailsService"

      class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">

      <property name="dataSource" ref="dataSource" />

   </bean>

 

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

 

 

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

1.4.1    JdbcDaoImpl

       JdbcDaoImpl允许我们从数据库来加载UserDetails,其底层使用的是SpringJdbcTemplate进行操作,所以我们需要给其指定一个数据源。此外,我们需要通过usersByUsernameQuery属性指定通过username查询用户信息的SQL语句;通过authoritiesByUsernameQuery属性指定通过username查询用户所拥有的权限的SQL语句;如果我们通过设置JdbcDaoImplenableGroupstrue启用了用户组权限的支持,则我们还需要通过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-manager alias="authenticationManager">

      <security:authentication-provider>

         <!-- 基于JdbcUserDetailsService实现,JdbcDaoImpl -->

         <security:jdbc-user-service data-source-ref="dataSource"/>

      </security:authentication-provider>

   </security:authentication-manager>

 

1.4.2    InMemoryDaoImpl

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

   <security:user-service>

      <security:user name="user" password="user" authorities="ROLE_USER"/>

   </security:user-service>

 

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

   <security:user-service properties="/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

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

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

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

分享到:
评论

相关推荐

    转载 :基于神经网络的分类决策树构造

    其中,分类问题是数据挖掘中的核心问题之一,其目的是找出能够将给定数据集分成不同类别的具体分类规则。当前主要存在两种解决分类规则提取问题的基本方法: 1. **基于符号处理的方法**:这类方法侧重于使用逻辑...

    转载:cglib动态代理介绍(一)

    1. **创建Enhancer对象**:Enhancer是CGlib的核心类,用于创建代理对象。我们可以通过调用`Enhancer.create()`方法来生成代理对象。 ```java Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(MyClass....

    为RMI实现类Jini的发现机制,转载自:51CTO

    这篇博客“为RMI实现类Jini的发现机制”探讨了如何在不使用完整Jini框架的情况下,利用RMI的核心概念来模拟Jini的服务发现机制。 首先,了解RMI的基本原理是至关重要的。RMI允许一个Java对象调用另一个在不同JVM上...

    javac的词法分析,转载自:百度文库

    Java编译器`javac`的词法分析阶段是其核心组成部分,负责将源代码分解为可理解的词法单元。通过识别关键字、标识符、字面量等,`javac`为解析和理解Java程序的结构打下基础。正确地进行词法分析对于保证代码的正确...

    Java字节码(.class文件)格式详解((转载)

    - **常量池**:存储各种字面量和符号引用,如字符串、类名、方法名等,是字节码的核心部分。 - **访问标志**:表示类或接口的访问权限和特性,如public、final、abstract等。 - **类和父类索引**:指明类的全限定...

    [转载] PV3D 全景的一个核心代码

    【PV3D全景的核心代码解析】 PV3D(Papervision3D)是一种基于Adobe Flash平台的开源3D图形库,它允许开发者在Web浏览器中创建交互式的三维场景。这个核心代码片段可能是PV3D实现全景展示的关键部分,下面我们将...

    行业分类-设备装置-用于铁芯分解自动装配机中的旋转转载支撑装置.zip

    铁芯是电机、变压器等电气设备的核心组件,其质量和效率直接影响到整个设备的性能。因此,对铁芯分解和装配过程的自动化控制至关重要。 首先,我们需要理解“铁芯分解自动装配机”的工作原理。这种机器设计的主要...

    Python 核心编程代码 https://blog.csdn.net/weixin-38566632/article/deta

    Python 核心编程 1. 数据类型 1.1 整型 int 1.2 浮点数 float 1.3 布尔类型 bool 1.4 字符串 str 1.5 列表 list 1.6 元组 tuple 1.7 集合 set 1.8 字典 dict 2. 逻辑结构、文件操作 2.1 分支结构和三元表达 2.2 循环...

    [转载]深入理解JVM

    这些字节码文件可以在任何安装了JVM的平台上运行,这是因为JVM作为平台的核心组件,负责加载、验证和执行字节码文件。 #### 二、Java虚拟机的体系结构 JVM的体系结构旨在确保其能够在各种不同的硬件和操作系统平台...

    本人提供SQL语句大全(转载) 12009年04月28日 星期二 19:35SQL语句大全(转载)

    综上所述,SQL作为数据库操作的核心语言,其语法的灵活运用和查询的优化是数据库管理和开发中不可忽视的重要环节。通过对上述知识点的理解和掌握,可以有效地提升SQL查询的效率和数据库系统的整体性能。

    2008车展专题转载

    【标题】"2008车展专题转载"与【描述】"2008车展专题转载IMG.zip"提及的核心知识点是2008年的汽车展览活动,以及与该活动相关的图像资源。在这个专题中,我们可以推测包含了一系列关于那次车展的重要图片,可能是...

    hibernate官方入门教程 (转载)

    标题“hibernate官方入门教程 (转载)”表明这是一份关于Hibernate框架的基础教学资源,可能是从官方文档或者其他可靠来源整理而来的,适合初学者了解和学习Hibernate。 描述中提到的“NULL”意味着没有具体的描述...

    教师资格考试培训资料《教学技能理论与实践》(2009-09-02112834)转载标签:微格教学板书课堂教学反.doc

    以下是其中的核心知识点: 1. 教学技能的观念包括运动方法说、行动说、结构说、能力说和知识说,体现了教学技能的多元性和综合性。 2. 教学技能是指教师基于已有知识经验,通过实践和反思形成的系列教学行为和心智...

    电子政务-一种自移式转载机电液控制系统.zip

    在这个特定的压缩包“电子政务-一种自移式转载机电液控制系统.zip”中,核心内容聚焦在自移式转载机的电液控制系统上,这是电子政务在工业自动化领域的一个应用实例。 自移式转载机是矿山开采中常用的一种机械设备...

    WCF 分布式开发转载

    在本篇文章中,我们将深入探讨WCF的核心概念、主要功能以及如何通过源代码学习WCF。 **一、WCF基本概念** 1. **服务**: 在WCF中,服务是对外提供功能的逻辑单元,可以通过各种协议(如HTTP、TCP等)进行通信。`WCF...

    中国人民大学核心期刊目录20171

    新华文摘(全文转载)人民出版社/二、人文社会科学国际 A 类期刊(809 种)1、国际 A+类期刊(35 种)SERIALNUMBERFULL JOURNAL

    java编写弹跳的小球源代码(转载)

    程序的核心类`jxiaoqiu`继承自`JFrame`,同时实现了`ActionListener`和`Runnable`接口。 - **`JFrame`**:提供了窗口的基础框架; - **`ActionListener`**:用于处理按钮点击事件; - **`Runnable`**:使得类能够被...

    Eclipse快捷键大全 转载

    ### Eclipse快捷键大全详解 Eclipse作为一款广泛使用的开源集成开发环境(IDE),为...以上快捷键涵盖了Eclipse中编辑、查找、重构和调试等核心功能,熟练掌握它们能够显著提升开发效率,让编程工作更加流畅高效。

Global site tag (gtag.js) - Google Analytics