Spring Security (03) - Introduction to Core Classes

Reference: http://www.cnblogs.com/fenglan/category/887982.html

Spring Security (03) - Introduction to Core Classes

 

content

1.1     Authentication

1.2     SecurityContextHolder

1.3     AuthenticationManager和AuthenticationProvider

1.3.1 Clear credentials after successful authentication

1.4     UserDetailsService

1.4.1 JdbcDaoImpl

1.4.2    InMemoryDaoImpl

1.5     GrantedAuthority

 

1.1     Authentication

       Authentication is an interface used to represent user authentication information. Before the user login authentication, the relevant information will be encapsulated as an object of the specific implementation class of Authentication. After the login authentication is successful, a more comprehensive information will be generated, including user permissions and other information. Authentication object, and then save it in the SecurityContext held by the SecurityContextHolder for subsequent programs to call, such as the identification of access rights.

 

1.2     SecurityContextHolder

       SecurityContextHolder is used to save SecurityContext. The SecurityContext contains the details of the user currently accessing the system. By default, SecurityContextHolder will use ThreadLocal to save SecurityContext, which means that in methods in the same thread, we can get the current SecurityContext from ThreadLocal. Because of the thread pool, if we clear the ThreadLocal every time the request is completed, then it is safer for us to store the SecurityContext in the ThreadLocal. Spring Security has already done this for us automatically, that is, the ThreadLocal of the current thread will be cleared after each request ends.

       A series of static methods are defined in SecurityContextHolder, and the internal logic of these static methods is basically implemented through the SecurityContextHolderStrategy held by SecurityContextHolder, such as getContext(), setContext(), clearContext(), etc. The default strategy is ThreadLocalSecurityContextHolderStrategy based on ThreadLocal. In addition, Spring Security also provides two types of strategy implementations, GlobalSecurityContextHolderStrategy and InheritableThreadLocalSecurityContextHolderStrategy. The former indicates that the same SecurityContext is used globally, such as a client of C/S structure; the latter uses InheritableThreadLocal to store SecurityContext, that is, child threads can use parent threads variables stored in .

       Generally speaking, we can use the default strategy , but if we want to change the default strategy, Spring Security provides us with two methods, both of which are achieved by changing the strategyName. Three different types of strategies are named MODE_THREADLOCAL, MODE_INHERITABLETHREADLOCAL and MODE_GLOBAL in SecurityContextHolder. The first way is to specify the strategy to be used through the static method setStrategyName() of SecurityContextHolder; the second way is to specify through the system property, where the property name defaults to "spring.security.strategy", and the property value corresponds to the strategy name.

       Spring Security uses an Authentication object to describe information about the current user. The SecurityContextHolder holds the SecurityContext of the current user, and the SecurityContext holds a reference to the Authentication representing the current user-related information. This Authentication object does not need to be created by ourselves. In the process of interacting with the system, Spring Security will automatically create the corresponding Authentication object for us, and then assign it to the current SecurityContext. But often we need to obtain the relevant information of the current user in the program, for example, the most common is to obtain the user name of the currently logged in user. Anywhere in the program, we can get the username of the current user in the following way.

   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);

   }

 

       Information representing the current user can be obtained through Authentication.getPrincipal(), which is usually an instance of UserDetails. Obtaining the username of the current user is a relatively common requirement. Regarding the above code, Spring Security has already implemented it for us in the implementation class in Authentication, so the easiest way to obtain the username of the current user should be as follows.

   public String getCurrentUsername() {

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

   }

 

       In addition, when calling SecurityContextHolder.getContext() to obtain SecurityContext, if the corresponding SecurityContext does not exist, Spring Security will create an empty SecurityContext for us and return it.

 

1.3     AuthenticationManager和AuthenticationProvider

       AuthenticationManager is an interface for handling authentication requests. There is only one method authenticate() defined in it, which only receives an Authentication object representing the authentication request as a parameter. If the authentication is successful, it will return an Authentication object that encapsulates the current user permissions and other information for return.

    Authentication authenticate(Authentication authentication) throwsAuthenticationException;

 

       In Spring Security, the default implementation of AuthenticationManager is ProviderManager, and it does not directly handle authentication requests by itself, but delegates to the list of AuthenticationProviders configured by it, and then uses each AuthenticationProvider for authentication in turn. If there is an AuthenticationProvider authentication result If it is not null, it means that the AuthenticationProvider has been successfully authenticated, and subsequent AuthenticationProviders will not continue to authenticate. Then directly use the authentication result of the AuthenticationProvider as the authentication result of the ProviderManager. If the authentication results of all AuthenticationProviders are null, it means that the authentication fails and a ProviderNotFoundException will be thrown. The most common way to verify the authentication request is to load the corresponding UserDetails according to the requested user name, and then compare whether the password of the UserDetails is the same as the password of the authentication request. If they are consistent, the authentication is passed. The DaoAuthenticationProvider inside Spring Security uses this method. It uses UserDetailsService internally to be responsible for loading UserDetails. UserDetailsService will be explained in the next section. After the authentication is successful, the loaded UserDetails will be used to encapsulate the Authentication object to be returned. The loaded UserDetails object contains information such as user permissions. The Authentication object returned by successful authentication will be saved in the current SecurityContext.

       When we are using NameSpace, the use of the authentication-manager element causes Spring Security to create a ProviderManager internally, and then we can add the AuthenticationProvider to it through the authentication-provider element. When defining the authentication-provider element, if you do not specify which AuthenticationProvider to associate with the ref attribute, Spring Security will use DaoAuthenticationProvider by default. After using NameSpace we don't need to declare ProviderManager anymore.

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

      <security:authentication-provider

         user-service-ref="userDetailsService"/>

   </security:authentication-manager>

 

       If we are not using NameSpace, then we should declare a ProviderManager in ApplicationContext.

 

1.3.1 Clear credentials after successful authentication

       By default, after successful authentication, ProviderManager will clear the credential information in the returned Authentication, such as password. So if you cache the returned Authentication information in a stateless application, then you will fail to use the cached information to authenticate later, because there is no credential information such as passwords. So you should take this into account when using cache. One solution is to set the eraseCredentialsAfterAuthentication property of the ProviderManager to false, or find a way to cache the credential information along with the cache.

 

1.4     UserDetailsService

       The return type of Authentication.getPrincipal() is Object, but in many cases it returns an instance of UserDetails. UserDetails is a core interface in Spring Security. It defines some methods for obtaining authentication-related information such as username, password, and permissions. Most of the UserDetails implementation classes used internally by Spring Security are built-in User classes, which can also be used directly if we want to use UserDetails. UserDetails are basically used when user information is needed in many places within Spring Security, such as when logging in and authenticating. During login authentication, Spring Security will obtain the corresponding UserDetails through the loadUserByUsername() method of UserDetailsService for authentication. After the authentication is passed, the UserDetails will be assigned to the principal of the Authentication that has passed the authentication, and then the Authentication will be stored in the SecurityContext. After that, if you need to use user information, you can obtain the principal of Authentication stored in the SecurityContext through the SecurityContextHolder.

       通常我们需要在应用中获取当前用户的其它信息,如Email、电话等。这时存放在Authentication的principal中只包含有认证相关信息的UserDetails对象可能就不能满足我们的要求了。这时我们可以实现自己的UserDetails,在该实现类中我们可以定义一些获取用户其它信息的方法,这样将来我们就可以直接从当前SecurityContext的Authentication的principal中获取这些信息了。上文已经提到了UserDetails是通过UserDetailsService的loadUserByUsername()方法进行加载的。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>

 

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

 

 

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

1.4.1    JdbcDaoImpl

       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-manager alias="authenticationManager">

      <security:authentication-provider>

         <!-- 基于Jdbc的UserDetailsService实现,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

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

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

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

 
(注:本文是基于Spring Security3.1.6所写)

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326247694&siteId=291194637