1 Spring Security详解(入门篇)

1 权限管理

1.1 权限管理概念

权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源。权限管理几乎出现在任何系统里面,前提是需要有用户和密码认证的系统。

在权限管理的概念中,有两个非常重要的名词:

  • 认证:通过用户名和密码成功登陆系统后,让系统得到当前用户的角色身份(用户登录后获取角色身份)。
  • 授权:系统根据当前用户的角色,给其授予对应可以操作的权限资源(系统根据角色授予用户权利)。

1.2 完成权限管理需要三个对象

  • 用户:主要包含用户名,密码和当前用户的角色信息,可实现认证操作。
  • 角色:主要包含角色名称,角色描述和当前角色拥有的权限信息,可实现授权操作。
  • 权限:权限也可以称为菜单,主要包含当前权限名称,url地址等信息,可实现动态展示菜单。

2 初识Spring Security

2.1 Spring Security概念

Spring Security是spring采用AOP思想,基于servlet过滤器实现的安全框架。它提供了完善的认证机制和方法级的授权功能。是一款非常优秀的权限管理框架。

2.2 Spring Security执行流程

用户请求访问资源-->拦截请求-->去认证-->授权(角色)-->认证且获得授权的用户-->访问资源(保护视图)

核心组件:(1)拦截请求 (2)认证  (3)授权(角色) (4)保护视图

2.3 简单入门案例

Spring Security博大精深,设计巧妙,功能繁杂,一言难尽,咱们还是直接上代码吧!

(1)导入jar包

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-taglibs</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>

(2)配置web.xml

只需要配置一个过滤器DelegatingFilterProxy。

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
          http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
	<display-name>Archetype Created Web Application</display-name>

    <!--SpringSecurity核心过滤器链-->
    <!--springSecurityFilterChain名词不能修改-->
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

(3)配置spring-security.xml

1.配置拦截要求的拦截器 2. 配置认证信息的来源并赋予认证用户的权限 。

    <!--
      auto-config="true"  表示自动加载springsecurity的配置文件
      use-expressions="true" 表示使用spring的el表达式来配置springsecurity
      -->
    <security:http auto-config="true" use-expressions="true">
        <!--拦截资源-->
        <!--
        pattern="/**" 表示拦截所有资源
        access="hasAnyRole('ROLE_USER')" 表示只有ROLE_USER角色才能访问资源
        -->
        <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')"/>

    </security:http>

    <!--设置Spring Security认证用户信息的来源-->
    <!--
    springsecurity默认的认证必须是加密的,加上{noop}表示不加密认证。
    -->
    <security:authentication-manager>
        <security:authentication-provider>
            <security:user-service>
                <security:user name="user" password="{noop}user"
                               authorities="ROLE_USER"></security:user>
                <security:user name="admin" password="{noop}admin"
                               authorities="ROLE_ADMIN"></security:user>
            </security:user-service>
        </security:authentication-provider>
    </security:authentication-manager>
</beans>

(4)启动运行

访问主界面的话,会进入Spring Security提供的登录界面。输入"user"和"user“之后(前面信息源存储的信息),登录成功。当然输入“admin”和“admin”是无法登录成功的,因为权限是"ROLE_ADMIN",而不是"ROLE_USER”。

输入"user","user"进入主页面

下面我们对入门案例中的各个组件进行逐一的说明

3 配置DelegatingFilterProxy

Spring Security借助一系列Servlet Filter来提供各种安全性功能。你可能会想,这是否意味着我们需要在web.xml中配置多个Filter呢?实际上,借助于Spring的小技巧,我们只需配置一个Filter就可以了。

DelegatingFilterProxy是一个特殊的Servlet Filter,它本身所做的工作并不多。只是将工作委托给一个javax.servlet.Filter实现类,这个实现类作为一个<bean>注册在Spring应用的上下文中,如下图所示。

在web.xml中配置 DelegatingFilterProxy:

   <!--SpringSecurity核心过滤器链-->
    <!--springSecurityFilterChain名词不能修改-->
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

在这里,最重要的是<filter-name>设置成了springSecurityFilterChain。这是因为我们马上就会将Spring Security配置在Web安全性之中,这里会有一个名为springSecurityFilterChain的Filter bean,DelegatingFilterProxy会将过滤逻辑委托给它。

DelegatingFilterProxy会拦截发往应用中的请求,并将请求委托给ID 为springSecurityFilterChain bean。springSecurityFilterChain本身是另一个特殊的Filter,它也被称为FilterChainProxy。它可以链接任意一个或多个其他的Filter。Spring Security依赖一系列Servlet Filter来提供不同的安全特性。但是,你几乎不需要知道这些细节,因为你不需要显式声明springSecurityFilterChain以及它所链接在一起的其他Filter。当我们启用Web安全性的时候,会自动创建这些Filter。常用的15个过滤器:

  1. SecurityContextPersistenceFilter:首当其冲的一个过滤器,作用之重要,自不必多言。SecurityContextPersistenceFilter主要是使用SecurityContextRepository在session中保存或更新一个SecurityContext,并将SecurityContext给以后的过滤器使用,来为后续filter建立所需的上下文。SecurityContext中存储了当前用户的认证以及权限信息。
  2. WebAsyncManagerIntegrationFilter:此过滤器用于集成SecurityContext到Spring异步执行机制中的
  3. header.HeaderWriterFilter:向请求的Header中添加相应的信息,可在http标签内部使用security:headers来控制
  4. CsrfFilter:csrf又称跨域请求伪造,SpringSecurity会对所有post请求验证是否包含系统生成的csrf的token信息,如果不包含,则报错。起到防止csrf攻击的效果。
  5. LogoutFilter:匹配URL为/logout的请求,实现用户退出,清除认证信息。
  6. UsernamePasswordAuthenticationFilter:认证操作全靠这个过滤器,默认匹配URL为/login且必须为POST请求。
  7. DefaultLoginPageGeneratingFilter:如果没有在配置文件中指定认证页面,则由该过滤器生成一个默认认证页面。
  8. DefaultLogoutPageGeneratingFilter:由此过滤器可以生产一个默认的退出登录页面
  9. BasicAuthenticationFilter:此过滤器会自动解析HTTP请求中头部名字为Authentication,且以Basic开头的头信息。
  10. Savedrequest.RequestCacheAwareFilter:通过HttpSessionRequestCache内部维护了一个RequestCache,用于缓存HttpServletRequest
  11. SecurityContextHolderAwareRequestFilter:针对ServletRequest进行了一次包装,使得request具有更加丰富的API
  12. AnonymousAuthenticationFilter:当SecurityContextHolder中认证信息为空,则会创建一个匿名用户存入到SecurityContextHolder中。spring security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。
  13. SessionManagementFilter:SecurityContextRepository限制同一用户开启多个会话的数量
  14. ExceptionTranslationFilter:异常转换过滤器位于整个springSecurityFilterChain的后方,用来转换整个链路中出现的异常
  15. FilterSecurityInterceptor:获取所配置资源访问的授权信息,根据SecurityContextHolder中存储的用户信息来决定其是否有权限。

4 拦截请求

在任何应用中,并不是所有的请求都需要同等程度地保护。有些请求需要认证,而另一些可能并不需要。有些请求可能只有具备特定权限的用户才能访问,没有这些权限的用户会无法访问。

在xml中配置拦截器:

    <security:http auto-config="true" use-expressions="true">
        <!--拦截资源-->
        <!--
        pattern="/**" 表示拦截所有资源
        access="hasAnyRole('ROLE_USER')" 表示只有ROLE_USER角色才能访问资源
        -->
        <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')"/>

    </security:http>

intercept-url:需要保护的路径;access:保护路径的方法

4.1 保护路径的方法

通过使用表中的方法,我们所配置的安全性能够不仅仅限于认证用户。例如,access=“hasAnyRole('ROLE_USER')”要求用户不仅需要认证,还要具备ROLE_SPITTER权限。

借助access()方法,我们也可以将SpEL作为声明访问限制的一种方式。

4.2 使用Spring表达式进行安全保护

借助access()方法,我们也可以将SpEL作为声明访问限制的一种方式(常用)。例如,access=“hasAnyRole('ROLE_USER')”表达式来声明具有“ROLE_USER”角色才能访问“/**”URL。让SpEL更强大的原因在于,hasRole()仅是Spring支持的安全相关表达式中的一种,下表列出了Spring Security支持的所有SpEL表达式。

5 用户认证及授权

5.1 用户认证

5.1.1 用户存储

用户认证的过程需要使用存储的用户信息,也就是用户名、密码以及其他信息,在进行认证决策的时候,会对其进行检索。Spring Security非常灵活,能够基于各种数据存储来认证用户。它内置了多种常见的用户存储场景,如内存、关系型数据库以及LDAP。

(1)使用基于内存的用户存储

(2)基于数据库表进行认证

(3)基于LDAP进行认证

 <security:authentication-manager>
        <security:authentication-provider>
            <security:user-service>
                <security:user name="user" password="{noop}user"
                               authorities="ROLE_USER"></security:user>
                <security:user name="admin" password="{noop}admin"
                               authorities="ROLE_ADMIN"></security:user>
            </security:user-service>
        </security:authentication-provider>
    </security:authentication-manager>

5.1.2 认证用户过程源代码详解

Spring Security自带的用户认证界面,还可以添加其他的认证配置

 

先看主要负责认证的过滤器UsernamePasswordAuthenticationFilter。

该类的主要是将填写的用户名和密码封装到了UsernamePasswordAuthenticationToken中,然后调用AuthenticationManager对象实现认证。

    public UsernamePasswordAuthenticationFilter() {
        super(new AntPathRequestMatcher("/login", "POST"));
    }

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }

            username = username.trim();
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

AuthenticationManager 由上面源码得知,真正认证操作在AuthenticationManager里面!

AuthenticationManager的实现类ProviderManager:

  • AuthenticationProvider这个对象,SpringSecurity针对每一种认证,什么qq登录啊,用户名密码登陆啊,微信登录啊都封装了一个AuthenticationProvider对象。
  • 循环所有AuthenticationProvider,匹配当前认证类型。
  • 找到对应认证类型就继续调用AuthenticationProvider对象完成认证业务。
  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        AuthenticationException parentException = null;
        Authentication result = null;
        Authentication parentResult = null;
        boolean debug = logger.isDebugEnabled();
        Iterator var8 = this.getProviders().iterator();

        while(var8.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var8.next();
            if (provider.supports(toTest)) {
                if (debug) {
                    logger.debug("Authentication attempt using " + provider.getClass().getName());
                }

                try {
                    result = provider.authenticate(authentication);
                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (AccountStatusException var13) {
                    this.prepareException(var13, authentication);
                    throw var13;
                } catch (InternalAuthenticationServiceException var14) {
                    this.prepareException(var14, authentication);
                    throw var14;
                } catch (AuthenticationException var15) {
                    lastException = var15;
                }
            }
        }

        if (result == null && this.parent != null) {
            try {
                result = parentResult = this.parent.authenticate(authentication);
            } catch (ProviderNotFoundException var11) {
            } catch (AuthenticationException var12) {
                parentException = var12;
                lastException = var12;
            }
        }

        if (result != null) {
            if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
                ((CredentialsContainer)result).eraseCredentials();
            }

            if (parentResult == null) {
                this.eventPublisher.publishAuthenticationSuccess(result);
            }

            return result;
        } else {
            if (lastException == null) {
                lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
            }

            if (parentException == null) {
                this.prepareException((AuthenticationException)lastException, authentication);
            }

            throw lastException;
        }
    }

5.2 用户授权

认证的同时已经配置了权限。

6 保护视图

当为浏览器渲染HTML内容时,你可能希望视图中能够反映安全限制和相关的信息。一个简单的样例就是渲染用户的基本信息(比如显示“您已经以……身份登录”)。或者你想根据用户被授予了什么权限,有条件地渲染特定的视图元素。Spring Security本身提供了一个JSP标签库,而Thymeleaf通过特定的方言实现了与Spring Security的集成。之后的博客再做介绍。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值