Spring Security 简单介绍与使用

1. Spring Security简介

Spring Security 基于Spring 框架,提供了一套web应用安全性的完整解决方案。

一般来说,Web 应用的安全性包括两部分:

  1. 用户认证(Authentication)
  2. 用户授权(Authorization)

在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。
在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。

2. SpringSecurity 过滤器链

SpringSecurity 采用的是责任链的设计模式,它有一条很长的过滤器链.

  1. WebAsyncManagerIntegrationFilter: 将Security上下文与Spring Web 中用于处理异步请求映射的WebAsyncManager进行集成。

  2. SecurityContextPersistenceFilter:在每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中,然后在该次请求处理完成之后,将 SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将 SecurityContextHolder 中的信息清除,例如在Session中维护一个用户的安全信息就是这个过滤器处理的。

  3. HeaderWriterFilter:用于将头信息加入响应中。

  4. CsrfFilter:用于处理跨站请求伪造。

  5. LogoutFilter:用于处理退出登录。

  6. UsernamePasswordAuthenticationFilter:用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自 /login 的请求。从表单中获取用户名和密码时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个过滤器的usernameParameter 和 passwordParameter 两个参数的值进行修改。

  7. DefaultLoginPageGeneratingFilter:如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面。

  8. BasicAuthenticationFilter:检测和处理 http basic 认证。

  9. RequestCacheAwareFilter:用来处理请求的缓存。

  10. SecurityContextHolderAwareRequestFilter:主要是包装请求对象request。

  11. AnonymousAuthenticationFilter:检测 SecurityContextHolder 中是否存在 Authentication 对象,如果不存在为其提供一个匿名 Authentication。

  12. SessionManagementFilter:管理 session 的过滤器

  13. ExceptionTranslationFilter:处理 AccessDeniedException 和 AuthenticationException 异常。

  14. FilterSecurityInterceptor:可以看做过滤器链的出口。

  15. RememberMeAuthenticationFilter:当用户没有登录而直接访问资源时, 从 cookie 里找出用户的信息, 如果 Spring Security 能够识别出用户提供的remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启。

3. 流程说明

  • 户端发起一个请求,进入 Security 过滤器链。
  • 当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler ,如果登出成功则到 logoutSuccessHandler 登出成功处理,如果登出失败则由 ExceptionTranslationFilter ;如果不是登出路径则直接进入下一个过滤器。
  • 当到 UsernamePasswordAuthenticationFilter 的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作,如果登录失败则到 AuthenticationFailureHandler 登录失败处理器处理,如果登录成功则到 AuthenticationSuccessHandler 登录成功处理器处理,如果不是登录请求则不进入该过滤器。
  • 当到 FilterSecurityInterceptor 的时候会拿到 uri ,根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层否则到 AccessDeniedHandler 鉴权失败处理器处理。

4. SpringSecurity 核心组件

  • SecurityContextHolder:提供对SecurityContext的访问
  • SecurityContext,:持有Authentication对象和其他可能需要的信息
  • AuthenticationManager:其中可以包含多个AuthenticationProvider
  • ProviderManager:对象为AuthenticationManager接口的实现类
  • AuthenticationProvider :主要用来进行认证操作的类 调用其中的authenticate()方法去进行认证操作
  • Authentication:Spring Security方式的认证主体
  • GrantedAuthority:对认证主题的应用层面的授权,含当前用户的权限信息,通常使用角色表示
  • UserDetails:构建Authentication对象必须的信息,可以自定义,可能需要访问DB得到
  • UserDetailsService:通过username构建UserDetails对象,通过loadUserByUsername根据userName获取UserDetail对象 (可以在这里基于自身业务进行自定义的实现 如通过数据库,xml,缓存获取等)
  1. Spring Security入门

使用SpringBoot+SpringSecurity+thymeleaf.
相关依赖:

<!--security 启动器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--thymeleaf 模板引擎-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--spring security结合thymeleaf-->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<!--web 启动器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

自定义登录页面:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>

<div class="container">

    <div class="row clearfix">

        <div class="col-md-12 column">
            <div class="page-header">
                <h1>
                    <small>用户登录</small>
                </h1>
            </div>
        </div>
        <div class="col-md-4 column" th:text="${msg}"></div>
    </div>

    <div class="clearfix" style="width: 40%;">

        <form method="post" th:action="@{/login}">
            <div class="form-group">
                <label for="txtUserName">用户名:</label>
                <input type="text" class="form-control" name="username" id="txtUserName" placeholder="username" required>
            </div>
            <div class="form-group">
                <label for="txtPassword">密 码:</label>
                <input type="password" class="form-control" name="password" id="txtPassword" placeholder="password" required>
            </div>
            <button type="submit" style="width: 100%;" class="btn btn-default">登 录</button>
        </form>
    </div>
</div>
</body>
</html>

默认页设置:

package cn.sivan.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 设置默认页
 * 使用WebMvcConfigurationSupport,会使用SpringBoot配置失效
 * 问题发现:无法访问static目录资源
 */
@Configuration
public class DefaultView implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index").setViewName("index");
    }
}
SpringSecurity配置类:
package cn.sivan.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

@EnableWebSecurity
//开启SpringSecurity授权注解
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringSecurity extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired
    private LogoutSuccessHandler logoutSuccessHandler;

    /**
     * 加密方式
     */
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * 身份认证管理器 可指定认证方式
     * 指定userDetailsService、authenticationProvider
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        /**
         * inMemoryAuthentication
         * 在内存中注册用户
         */

        //指定密码加密方式
        auth.inMemoryAuthentication().passwordEncoder(passwordEncoder)
                .withUser("admin").password(passwordEncoder.encode("123")).roles("ADMIN");

        //忽略密码加密{noop}
        //auth.inMemoryAuthentication()
        //        .withUser("user").password("{noop}123").roles("USER");
    }

    /**
     * 安全设置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()

                //不定向参数,匹配url规则
                //设置静态资源允许访问
                .antMatchers("/css/**", "/js/**", "/favicon.ico").permitAll()

                //指定角色可访问
                //.antMatchers("/user/**").hasAnyRole("ADMIN")
                //.antMatchers("/order/**").hasAnyRole("USER", "ADMIN")

                //使用正则表达式进行匹配
                //.regexMatchers(".+[.]js").permitAll()

                //其他所有请求 都需要认证
                .anyRequest().authenticated()

                //统一前缀 等同antMatchers("/prefix/demo")
                //.mvcMatchers("demo").servletPath("/prefix").permitAll()

                .and()

                //进行有关登录的一些设置
                .formLogin()
                    //指定登录页面
                    .loginPage("/login/user")
                    //指定登录参数 用户名、密码字段
                    //.usernameParameter("username")
                    //.passwordParameter("password")
                    //指定登录地址
                    .loginProcessingUrl("/login")

                    //登录成功页
                    //如果是认证失败跳到登录的,登录成功后,会回到想去的页面,比较友好
                    //第二个参数设置为true,等于failureForwardUrl
                    .defaultSuccessUrl("/")

                    //登录成功后就跳到指定页面
                    //注意:html不支持响应头带有post的应答包
                    //.successForwardUrl("/")

                    //登录失败跳转
                    .failureForwardUrl("/login/failure")
                    //.failureUrl("/login/failure")

                    //登录成功的Handler
                    //.successHandler(authenticationSuccessHandler)

                    .permitAll()
                    .and()

                //注销登录
                .logout()

                    //用户注销地址
                    .logoutUrl("/logout")

                    //注销成功后的页面
                    .logoutSuccessUrl("/login/user")

                    //注销成功的Handler
                    //.logoutSuccessHandler(logoutSuccessHandler)

                    //session失效
                    .invalidateHttpSession(true)
                    .and()

                //跨域伪造请求
                .csrf().disable();
    }
}

自定义认证成功Handler实例:

package cn.sivan.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * 自定义认证成功Handler
 */
@Component
public class CustomizeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        Map<String, Object> map = new HashMap<>();
        map.put("code", "200");
        map.put("msg", "登录成功");
        map.put("path", request.getRequestURI());

        out.write(new ObjectMapper().writeValueAsString(map));
        out.flush();
        out.close();
    }
}

SpringSecurity注解使用示例:

@Controller
@RequestMapping("/order")
public class OrderController {

    @RequestMapping("/list")
    @Secured({"ROLE_USER", "ROLE_ADMIN"})
    public String list() {
        return "order/orderList";
    }
}

thymeleaf结合security的实例:

<!DOCTYPE html>
<html lang="zh" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>

<body sec:authorize="isAuthenticated()">

<!--获取登录用户名-->
<p>登录名:<span sec:authentication="name"></span></p>

<!--注销-->
<form th:action="@{/logout}" method="post">
    <button type="submit">退出登录</button>
</form>

<!--判断对应角色-->
<span sec:authorize="hasRole('ROLE_ADMIN')">
    <a th:href="@{/user/list}">用户管理</a>
</span>
<span sec:authorize="hasAnyRole('ROLE_ADMIN','ROLE_USER')">
    <a th:href="@{/order/list}">订单管理</a>
</span>

</body>
</html>
posted @ 2020-09-09 17:41  宁川  阅读(518)  评论(0)    收藏  举报