码迷,mamicode.com
首页 > 编程语言 > 详细

Spring Security:核心组件说明

时间:2014-08-26 13:24:06      阅读:315      评论:0      收藏:0      [点我收藏+]

标签:style   blog   http   color   os   java   使用   io   strong   

理解了Spring Security核心组件说明,Spring Security的后续学习和使用将不再是难题。

1、Spring Security 核心

 

SecurityContextHolder、SecurityContext、Authentication、GrantedAuthority、UserDetails

 

Authentication:代表了Spring Security中的当事人。

SecurityContext:拥有了Authentication、请求相关的信息。

SecurityContextHodler:用于获取SecurityContext。

GrantedAuthority:代表在应用程序中给当事人授予的权限。

UserDetails:用户详细信息。其实就是一个JavaBean。

UserDetailsService:UserDetails相关的业务处理。

 

这几个是Spring Security的核心,其它的API都是围绕这些API展开的,都是为它们服务的。

 

 

 

2、身份认证Authentication

 

2.1、一般的身份认证

从Spring Security核心部分,对Spring Security有一个笼统概念了,那该怎么理解上面说的呢?

 

通常情况下,我们的系统都是这样的:

1、用户输入用户名、密码登录

2、系统对用户名、密码进行验证

3、获取用户上下文信息(角色列表等等)

4、获取相关操作权限

 

对于上面说的前三条,用Spring Security来处理,就是:

1、用户名、密码组合生成一个Authentication对象(也就是UsernamePasswordAuthenticationToken对象)。

2、生成的这个token对象会传递给一个AuthenticationManager对象用于验证。

3、当成功认证后,AuthenticationManager返回一个Authentication对象。

4、接下来,就可以调用

SecurityContextHodler.getContext().setAuthentication(…)。

 

 

为了更好的理解,下面就写一个例子:

 

bubuko.com,布布扣
package com.springsecurity.java.test;

 

import java.io.BufferedReader;

import java.io.InputStreamReader;

import java.util.ArrayList;

import java.util.List;

 

import org.springframework.security.authentication.AuthenticationManager;

import org.springframework.security.authentication.BadCredentialsException;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

import org.springframework.security.core.Authentication;

import org.springframework.security.core.AuthenticationException;

import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.core.authority.SimpleGrantedAuthority;

import org.springframework.security.core.context.SecurityContextHolder;

 

public class AuthenticationExample {

   private static SimpleAuthenticationManager samgr = new SimpleAuthenticationManager();

 

   public static void main(String[] args) {

      try {

// 用户输入用户名、密码:

        BufferedReader in = new BufferedReader(new InputStreamReader(

              System.in));

        System.out.println("Please enter your username:");

        String name = in.readLine();

        System.out.println("Please enter your password:");

        String password = in.readLine();

// 接下来是系统进行身份认证的过程:

//1、将用户名、密码封装成一个token

        Authentication token = new UsernamePasswordAuthenticationToken(

              name, password);

//2、将token传给AuthenticationManager进行身份认证

//3、认证完毕,返回一个认证后的身份:

        Authentication result = samgr.authenticate(token);

// 认证后,存储到SecurityContext里 :    SecurityContextHolder.getContext().setAuthentication(result);

      } catch (Exception ex) {

        System.out.println("认证失败");

      }

 

// 从SecurityContext读取认证的身份:

System.out.println(SecurityContextHolder.getContext()

           .getAuthentication());

   }

}

 

class SimpleAuthenticationManager implements AuthenticationManager {

   static final List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>();

   static {

      AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));

   }

 

   public Authentication authenticate(Authentication auth)

        throws AuthenticationException {

      if (auth.getName().equals(auth.getCredentials())) {

        return new UsernamePasswordAuthenticationToken(auth.getName(),

              auth.getCredentials(),AUTHORITIES);

      }

      throw new BadCredentialsException("Bad Credentials");

   }

 

}

 
View Code

 

2.2、Web Application中如何进行身份认证呢?

1、用户在首页上点击某个链接

2、后台处理时,先判断是否是要访问一个受保护的资源

3、如果是受保护的资源,判断用户是否登录,是否有这个资源的访问权限

4、如果用户没有登录,返回一个登录页面给用户

5、用户输入username、password,然后登录

6、接下来开始上面的身份认证过程

 

在Web Application环境下,将上述1-4过程由AuthenticationEntryPoint来处理。

 

 

如何存储已认证的用户呢?

    用户要访问另外的资源时,肯定要判断是否有该资源的访问权限,在判断是否有访问权限前,一般要用户先登录系统(要对用户的身份进行认证)的。如果用户已经成功登录,只需要判断是否有访问权即可。

    在一般的Web Application中(不使用Spring Security),我们通常会将用户信息存储到HttpSession中。

    如果在Web Application系统中,加上了Spring Security呢?

做法没什么两样,还是将这个信息存储在HttpSession中。但对于无状态的RESTful Web Service不是这样做的。

 

 

3、授权Authorization(Access Control)

 

身份认证Authentication保证了用户可访问系统。权限认证(授权保证了用户可以访问系统中的资源)。

    在用户的一次资源访问中,这两个过程是不可少的。Access Control就是决定你这个请求是否被允许,它是在身份认证之后,访问资源之前进行的。

 

用户请求-->身份认证-->授权-->资源访问-->响应

 

身份认证过程由AuthenticationManager来处理,授权决定则是由AccessDecisionManager来处理的。

void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException;

 

Spring在进行授权时,充分利用了Spring的核心之一:AOP。通过前段时间关于AOP的研究,AOP在程序中一般体现为Filter或者是Interceptor,其中更多的是使用Interceptor。 

 

 

上面的decide方法需要三个参数:

·Authentication就是已经通过认证的Authentication对象。上面的学习已经可以理解。

·Object obj  是代表方法调用(MethodInvocation)或者请求处理(Action的Handler),在接下来的AbstractSecurityInterceptor中说明。

·configAttributes则是相关的特性配置。下面会有说明。

 

AbstractSecurityInterceptor

 

Security中关于授权部分使用了AOP,所以就不得不了解一下AbstractSecurityInterceptor。

 

Interceptor一般都会提供invoke方法。这个类是抽象类,没有提供,使用时用的是它的子类,在子类中提供了invoke方法。而在AccessDecisionManager的decide方法中的第二个参数,就是invoke方法的参数MethodInvocation对象。

 

ConfigAttribute

    其实就是对某个拦截器配置一些访问属性。举个例子:

某个拦截器interceptor,配置访问属性有ROLE_A, ROLE_B,如果一个用户经过认证后他有一个Authentication为ROLE_A,那么他的这个请求就会被拦截器interceptor处理。

 

再说的通俗点就是:设置某个拦截器能够处理哪些身份的用户的请求。

 

AbstractSecurityInterceptor的执行流程

 

1、查找有哪些configAttributes与当前的请求相关联。

2、提交secure object(就是前面说的MethodInvocation对象)、当前的Authentication(就是已经认证好的身份)、以及1中找到的那些configAttributes,提交给AccessDecisionManager用于授权。

3、选择性的改变用户的身份去进行验证。这是因为用户身份的多样性的需要。

4、Secure Object(methodInvocation 对象执行),也就是执行我们在action中写的Handler。

5、如果配置了AfterInvocationManager,那么AfterInvocationManager也会执行的。

 

这个流程是官方文档中说明的,已经很清楚了。为了更清新的了解这个过程,还是查看一下源码吧:

在AbstractSecurityInterceptor的子类MethodSecurityInterceptor中:

public Object invoke(MethodInvocation mi) throws Throwable {

// 预先处理

        InterceptorStatusToken token = super.beforeInvocation(mi);

 

        Object result;

        try {

// 真实方法调用,也就是我们写的action调用

            result = mi.proceed();

        } finally {

            super.finallyInvocation(token);

        }

// afterInvocationManager处理

        return super.afterInvocation(token, result);

    }

 


这段代码与上面的流程说明对应起来,那就应该是上面说的流程中的1、2、3对应了这段代码中的 预先处理部分。就来看看beforeInvocation:
 

// 参数object就是方法调用

protected InterceptorStatusToken beforeInvocation(Object object) {

        Assert.notNull(object, "Object was null");

        final boolean debug = logger.isDebugEnabled();

 

        if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {

            throw new IllegalArgumentException("Security invocation attempted for object "

                    + object.getClass().getName()

                    + " but AbstractSecurityInterceptor only configured to support secure objects of type: "

                    + getSecureObjectClass());

        }

// 收集与方法调用相关的特性配置

        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);

 

        if (attributes == null || attributes.isEmpty()) {

            if (rejectPublicInvocations) {

                throw new IllegalArgumentException("Secure object invocation " + object +

                        " was denied as public invocations are not allowed via this interceptor. "

                                + "This indicates a configuration error because the "

                                + "rejectPublicInvocations property is set to ‘true‘");

            }

 

            if (debug) {

                logger.debug("Public object - authentication not attempted");

            }

 

            publishEvent(new PublicInvocationEvent(object));

 

            return null; // no further work post-invocation

        }

 

        if (debug) {

            logger.debug("Secure object: " + object + "; Attributes: " + attributes);

        }

 

        if (SecurityContextHolder.getContext().getAuthentication() == null) {

            credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",

                    "An Authentication object was not found in the SecurityContext"), object, attributes);

        }

// 取得该用户已验证的身份

        Authentication authenticated = authenticateIfRequired();

 

        // Attempt authorization

        try {

           

// 进行用户授权

this.accessDecisionManager.decide(authenticated, object, attributes);

        }

        catch (AccessDeniedException accessDeniedException) {

            publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException));

 

            throw accessDeniedException;

        }

 

        if (debug) {

            logger.debug("Authorization successful");

        }

 

        if (publishAuthorizationSuccess) {

            publishEvent(new AuthorizedEvent(object, attributes, authenticated));

        }

// 切换为其他身份

        // Attempt to run as a different user

        Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);

 

        if (runAs == null) {

            if (debug) {

                logger.debug("RunAsManager did not change Authentication object");

            }

 

            // no further work post-invocation

            return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);

        } else {

            if (debug) {

                logger.debug("Switching to RunAs Authentication: " + runAs);

            }

            SecurityContext origCtx = SecurityContextHolder.getContext();

            SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());

            SecurityContextHolder.getContext().setAuthentication(runAs);

 

            // need to revert to token.Authenticated post-invocation

            return new InterceptorStatusToken(origCtx, true, attributes, object);

        }

    }

 

4、Localization异常消息的国际化 

 

Spring Security分为了身份认证和授权两个过程,上面已经讲明 。在这两个过程中,发生异常是在所难免的。Spring为此提供了异常消息的国际化支持。

 

Spring Security:核心组件说明

标签:style   blog   http   color   os   java   使用   io   strong   

原文地址:http://www.cnblogs.com/f1194361820/p/3936938.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!