【Spring Boot】Spring Boot的Security安全控制

Spring Boot的Security安全控制

Spring Security是一个强大且高度可定制的身份验证和访问控制框架,完全基于Spring的应用程序的标准,Spring Security为基于JAVA EE的企业应用程序提供了一个全面的安全解决方案。

Spring Security是什么?

Spring Security提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC(控制反转)和AOP(面向切面编程)功能,为应用系统提供安全访问控制功能。
安全框架主要包括两个操作:

  • 认证(Authentication):确定用户可以访问当前系统
  • 授权(Authorization):确定用户在当前系统中是否能够执行某个操作,
    Spring Security包括多个模块:
  • 核心模块(spring-security-core.jar):包含核心的验证和访问控制类以及接口、远程支持和基本的配置API。
  • 远程调用(spring-security-remoting.jar):提供与Spring Remoting的集成
  • Web页面(spring-security-web.jar):包括网站安全相关的基础代码,包括Spring Security网页验证服务和基于URL的访问控制
  • 配置(spring-security-config.jar):包含安全命令空间的解析代码
  • LDAP(spring-security-ldap.jar):LDAP验证和配置代码,如果使用LDAP验证和管理LDAP用户实体,需要使用该模块
  • ACL访问控制表(spring-security-acl.jar):ACL专门的领域对象的实现,用于在应用程序中对特定域对象实例应用安全性。
  • CAS(spring-security-cas.jar):Spring Security的CAS客户端集成,用于CAS的SSO服务器使用Spring Security网页验证
  • OpenID(spring-security-openid.jar):OpenID网页验证支持,使用外部的OpenID服务器验证用户。
  • Test(spring-security-test.jar):支持Spring Security的测试。

Spring Security基础

Security适配器

在Spring Boot当中配置Spring Security非常简单,创建一个自定义类集成WebSecurityConfigurerAdapter,并在该类中使用@EnableWebSecurity注解,就可以通过重写config方法类配置所需要的安全配置
WebSecurityConfigurerAdapter是Spring Security为Web应用提供的一个适配器,实现了WebSecurityConfigurer接口,提供了两个方法用于重写开发者需要的安全配置:

protected void configure(HttpSecurity httpSecurity) throws Exception{}
protected void configure(AuthenticationManagerBuilder auth) throws Exception{}

configure(HttpSecurity httpSecurity)方法中可以通过HttpSecurity的authorizeRequests()方法定义哪些URL需要被保护,哪些不需要被保护;通过formLogin()方法定义当需要用户登录的时候,跳转到的登录页面。
configure(AuthenticationManagerBuilder auth)方法用于创建用户和用户的角色。

用户认证

Spring Security是通过configureGlobal(AuthenticationManagerBuilder auth)完成用户认证的。使用AuthenticationManagerBuilder的inMemoryAuthentication()方法可以添加用户,并给用户指定权限。
例如:

@Authwired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
	auth.inMemoryAuthentication().withUser("fkit").password("1234455").roles("USER");
	auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN","DBA");
}

需要注意的是,Spring Security保存用户权限的时候,会默认使用“ROLE_”,也就是说,"USER"实际上是“ROLE_UAER“。”ADMIN”实际上是“ROLE_ADMIN”

用户授权

Spring Security是通过configure(HttpSecurity http)完成用户授权的。
HttpSecurity的authorizeRequests()方法有多个子节点,每个macher按照它们的声明顺序执行,指定用户可以访问的多个URL模式。

  • antMatchers使用Ant风格匹配路径
  • regrexMatchers使用正则表达式匹配路径
    在匹配了请求路径后,可以针对当前用户的信息对请求路径进行安全处理。如下表所示的安全处理方法
方法 用途
anyRequest 匹配所有请求路径
access(String) Spring EL表达式结果为true时可以访问
anonymous() 匿名可以访问
denyAll() 用户不能访问
fullyAuthenticated() 用户完全认证可以访问(非remember-me下自动登录)
hasAnyAuthority(String…) 如果有参数,参数表示权限,则其中任何一个权限可以访问
hasAnyRole(String…) 如果有参数,参数表示角色,则其中任何一个角色可以访问
hasAuthority(String…) 如果有参数,参数表示权限,则其权限可以访问
hasIpAddress(String…) 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
hasRole(String) 如果有参数,参数表示角色,则其角色可以访问
permitAll() 用户可以任意访问
rememberMe() 允许通过remember-me登录的用户访问
authenticated() 用户登录后可以访问

示例代码如下:

@Override
protected void configure(HttpSecurity http) throws Exception{
	http.authorizeRequests() // 开始请求权限配置
	.antMatchers("/login").permitAll() // 请求匹配/login,所有用户都可以访问
	.antMatchers("/","/home").hasRole("USER") // 请求匹配/和/home,拥有ROLE_USER角色的用户可以访问
	.antMatchers("/admin/***").hasAnyRole("ADMIN","DBA") //请求匹配/admin/**,拥有ROLE_ADMIN或ROLE_DBA角色的用户可以访问
	.anyRequst().authenticated(); //其余所有的请求都需要认证之后才可以访问。
}

HttpSecurity还可以设置登录的行为,示例代码如下:

@Override
protected void configure(HttpSecurity http) throws Exception{
	http.authorizeRequests()
	.antMatchers("/login").permitAll()
	.antMatchers("/","/home").hasRole("USER")
	.antMatchers("/admin/***").hasAnyRole("ADMIN","DBA")
	.anyRequest().authenticated()
	.and()
	.formLogin()// 开始设置登录操作
	.loginPage("/login") //设置登录页面的访问地址
	.usernameParameter("/loginName").passwordParameter("password") //登录时接收传递的参数loginName的值作为用户名,接收传递参数的password的值作为密码
	.defaultSuccessUrl("/success") //指定登录成功后转向的页面
	.failureUrl("/login?error") //指定登录失败后转向的页面和传递的参数
	.and()
	.logout() //设置注销操作
	.permitAll() //所有用户都可以访问
	.and()
	.exceptionHandling().accessDeniedPage("/accessDenied"); // 指定异常处理页面
}

Spring Security核心类

Spring Security核心类包括Authentication、SecurityContextHolder、UserDetails、UserDetailsService、GrantedAuthority、DaoAuthenticationProvider和PasswordEncoder。

  1. Authentication: 用来表示用户认证的信息,在用户登录认证之前,Spring Security会将相关信息封装为一个Authentication具体实现类的对象,在登录认证成功之后又会生成一个信息更全面,包含用户权限等信息的Authentication对象,然后把它保存在SecurityContextHolder所持有的SecurityContext中,供后续的程序进行调用,如访问权限的鉴定等。
  2. SecurityContextHolder:用来保存SecurityContext的。SecurityContext中包含当前所访问系统的用户的详细信息。默认情况下,SecurityContextHolder将使用ThreadLocal来保存SecurityContext,这也意味着在处于同一线程的方法中,可以从ThreadLocal获取到当前的SecurityContext。
    Spring Security使用一个Authentication对象来描述当前用户的相关信息。SecurityContextHolder中持有的是当前用户的SecurityContext,而SecurityContext持有的是代表当前用户相关信息的Authentication的引用。这个Authentication对象不需要我们自己去创建,在与系统交互的过程中,Spring Security会自动为我们创建相应的Authentication对象,然后赋值给当前的SecurityContext。但是往往我们需要在程序中获取当前用户的相关信息,比如最常见的是获取当前登录用户的用户名。例如:
String username = SecurityContextHolder.getContext().getAuthentication().getName();
  1. UserDetails:是一个Spring Security的一个核心接口。其中定义了一些可以获取用户名、密码、权限等与认证相关的信息的方法。Sprign Security内部使用的UserDetails实现类大都是内置的User类,要使用UserDetails,也可以直接使用该类。在Spring Security内部,很多需要使用用户信息的时候,基本上都是使用UserDetails,比如在登录认证的时候。
    登录认证的时候Spring Security会通过UserDetailsService的loadUserByUsername()方法获取对应的UserDetails进行认证,认证通过后会将该UserDetails赋给认证通过的Authentication的principal,然后再把该Authentication存入到SecurityContext中。之后如果需要使用用户信息的时候就是通过SecurityContextHolder获取存放在SecurityContext中的Authentication的principal。
    通常我们需要在应用中获取当前用户的其它信息,如Email、电话等。这时存放在Authentication的principal中只包含有认证相关信息的UserDetails对象可能就不能满足我们的要求了。这时我们可以实现自己的UserDetails,在该实现类中我们可以定义一些获取用户其它信息的方法,这样将来我们就可以直接从当前SecurityContext的Authentication的principal中获取这些信息了。UserDetails是通过UserDetailsService的loadUserByUsername()方法进行加载的。UserDetailsService也是一个接口,我们也需要实现自己的UserDetailsService来加载我们自定义的UserDetails信息,然后把它指定给AuthenticationProvider即可。
  2. UserDetailsService
    通过Authentication.getPrincipal()的返回类型是Object,但很多情况下其返回的其实是一个UserDetails的实例。UserDetails是Spring Security中一个核心的接口。其中定义了一些可以获取用户名、密码、权限等与认证相关的信息的方法。Spring Security内部使用的UserDetails实现类大都是内置的User类,我们如果要使用UserDetails时也可以直接使用该类。在Spring Security内部很多地方需要使用用户信息的时候基本上都是使用的UserDetails,比如在登录认证的时候。登录认证的时候Spring Security会通过UserDetailsService的loadUserByUsername()方法获取对应的UserDetails进行认证,认证通过后会将该UserDetails赋给认证通过的Authentication的principal,然后再把该Authentication存入到SecurityContext中。之后如果需要使用用户信息的时候就是通过SecurityContextHolder获取存放在SecurityContext中的Authentication的principal。
  3. GrantedAuthority
    Authentication的getAuthorities()可以返回当前Authentication对象拥有的权限,即当前用户拥有的权限。其返回值是一个GrantedAuthority类型的数组,每一个GrantedAuthority对象代表赋予给当前用户的一种权限。GrantedAuthority是一个接口,其通常是通过UserDetailsService进行加载,然后赋予给UserDetails的。
    GrantedAuthority中只定义了一个getAuthority()方法,该方法返回一个字符串,表示对应权限的字符串表示,如果对应权限不能用字符串表示,则应当返回null。
    Spring Security针对GrantedAuthority有一个简单实现SimpleGrantedAuthority。该类只是简单的接收一个表示权限的字符串。Spring Security内部的所有AuthenticationProvider都是使用SimpleGrantedAuthority来封装Authentication对象。
  4. DaoAuthenticationProvider
    Spring Security默认会使用DaoAuthenticationProvider实现AuthenticationProvider接口,专门进行用户认证的处理。DaoAuthenticationProvider在进行认证的时候需要一个UserDetailsService来获取用户的信息UserDetails,其中包括用户名、密码和所拥有的权限等。如果需要改变认证的方式,开发者可以实现自己的AuthenticationProvider。
  5. PasswordEncoder
    在Spring Security中,对密码的加密都是由PasswordEncoder来完成的。在Spring Security中,已经对PasswordEncoder有了很多实现,包括MD5加密、SHA-256加密等,开发者只需要拿来用就好。在DaoAuthenticationProvider中,有一个就是PasswordEncoder属性,密码加密功能主要靠它来完成。
    在Spring的官方文档中,如果开发一个新的项目,BCryptPasswordEncoder是较好的选择。BCryptPasswordEncoder使用BCrypt的强散列哈希加密实现,并可以由客户端指定加密的强度,强度越高安全性自然就越高。

Spring Security的验证机制

Spring Security大体上是由一堆Filter实现的,Filter会在Spring MVC前拦截请求。Filter包括登出Filter(LogoutFilter)、用户名密码验证Filter(UsernamePasswordAuthenticationFilter)之类。Filter再交由其他组件完成细分的功能,最常用的UsernamePasswordAuthenticationFilter会持有一个AuthenticationManager引用,AuthenticationManager是一个验证管理器,专门负责验证。但AuthenticationManager本身并不做具体的验证工作,AuthenticationManager持有一个AuthenticationProvider集合,AuthenticationProvider才是验证工作的组件,验证成功或失败之后调用对应的Handler。

Spring Boot的支持

  • Spring Boot 通过org.springframework.boot.autoconfigure.security包对Spring Security提供了自动装配的支持,其工作主要通过SecurityAutoConfiguration和SecurityProperties两个类来完成自动装配。
    SecurityProperties类使用以“security"为前缀的属性配置Spring Security相关的配置,具体内容如下:
security.user.name=user # 默认用户名
security.user.password= # 默认用户密码
security.user.role=USER # 默认用户角色
security.require-ssl=false # 是否需要ssl支持
security.enable-csrf=false # 是否开启csrf支持,默认关闭
# 默认basic认证设置
security.basic.enabled=true
security.basic.realm=Spring
security.basic.path=  #/**
# 默认headers认证设置
security.headers.xss=false
security.headers.cache=false
security.headers.frame=false
security.headers.contentType=false
security.headers.hsts=all
security.sessions=stateless # Session创建策略(always,never,if_required,stateless)
security.ignored=false # 安全策略
  • Spring Boot自动配置一个DefaultSecurityFilterChain过滤器,用来忽略/css/**, /js/**、/images/**、/webjars/**、/**/favicon.ico、/error等文件的拦截。
  • Spring Boot自动注册Security的过滤器。

简单Spring Boot Security应用

创建一个新的Maven项目,命名为SpringSecurity。按照Maven项目的规范,在src/main/下新建一个名为resources的文件夹,并在src/main/resources下新建static和templates两个文件夹。

  1. 修改pom.xml文件,并引入静态文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.security</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Sprign Security Demo project for Spring Boot</description>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
            <scope>true</scope>
        </dependency>
        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
            <version>2.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  1. 开发用于测试的html页面
    login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="zh">
<head>
    <meta charset="UTF-8"/>
    <title>Spring Boot Security 示例</title>
    <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
    <link rel="stylesheet" th:href="@{css/bootstrap-theme.min.css}"/>
    <script type="text/javascript" th:src="@{js/jQuery-3.4.1.min.js}"></script>
    <script type="text/javascript" th:src="@{js/bootstrap.min.js}"></script>
    <script type="text/javascript">
        $(function () {
            $("#loginBtn").click(function () {
                var loginName = $("#loginName");
                var password = $("#password");
                var msg = "";
                if (loginName.val() === "") {
                    msg = "登录名不能为空!";
                    loginName.focus();
                } else if (password.val() === "") {
                    msg = "密码不能为空!";
                    password.focus();
                }
                if (msg !== "") {
                    alert(msg);
                    return false;
                }
                $("#loginForm").submit();
            });
        });
    </script>
</head>
<body>
<div class="panel panel-primary">
    <!-- .panel-heading 面板头信息 -->
    <div class="panel-heading">
        <!-- .panel-title 面板标题 -->
        <h3 class="panel-title">简单Spring Boot Security示例</h3>
    </div>
</div>
<div id="mainWrapper">
    <div class="login-container">
        <div class="login-card">
            <div class="login-form">
                <form class="form-horizontal" th:action="@{/login}" method="post" id="loginForm">
                    <!-- 用户名或密码错误提示-->
                    <div th:if="${param.error != null}">
                        <div class="alert alert-danger">
                            <p><font color="red">用户名或密码错误!</font></p>
                        </div>
                    </div>
                    <!-- 注销提示 -->
                    <div th:if="${param.logout !=null}">
                        <div class="alert alert-success">
                            <p><font color="red">用户已注销成功!</font></p>
                        </div>
                    </div>
                    <div class="input-group input-sm">
                        <label class="input-group-addon"><i class="fa fa-user"></i></label>
                        <input class="form-control" placeholder="请输入用户名" type="text" name="loginName" id="loginName"/>
                    </div>
                    <div class="input-group input-sm">
                        <label class="input-group-addon"><i class="fa fa-lock"></i></label>
                        <input class="form-control" placeholder="请输入密码" type="password" name="password" id="password"/>
                    </div>
                    <div class="form-actions">
                        <input id="loginBtn" type="button" class="btn btn-block btn-primary btn-default" value="登录"/>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>
</body>
</html>

home.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <meta charset="UTF-8">
    <title>home 页面</title>
    <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
    <link rel="stylesheet" th:href="@{css/bootstrap-theme.min.css}"/>
    <script type="text/javascript" th:src="@{js/jQuery-3.4.1.min.js}"></script>
    <script type="text/javascript" th:src="@{js/bootstrap.min.js}"></script>
</head>
<body>
<div class="panel panel-primary">
    <div class="panel-heading">
        <h3 class="panel-title">Home 页面</h3>
    </div>
</div>
<h3>欢迎[<font color="red"><span th:text="${user}">用户名</span></font> ]访问Home页面!
    您的权限是<font color="red"><span th:text="${role}">权限</span></font><br/><br/>
    <a href="admin">访问admin页面</a><br/><br/>
    <a href="logout">安全退出</a>
</h3>
</body>
</html>

admin.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <meta charset="UTF-8">
    <title>admin 页面</title>
    <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
    <link rel="stylesheet" th:href="@{css/bootstrap-theme.min.css}"/>
    <script type="text/javascript" th:src="@{js/jQuery-3.4.1.min.js}"></script>
    <script type="text/javascript" th:src="@{js/bootstrap.min.js}"></script>
</head>
<body>
<div class="panel panel-primary">
    <div class="panel-heading">
        <h3 class="panel-title">Admin 页面</h3>
    </div>
</div>
<h3>欢迎[<font color="red"><span th:text="${user}">用户名</span></font> ]访问Admin页面!
    您的权限是<font color="red"><span th:text="${role}">权限</span></font><br/><br/>
    <a href="dba">访问dba页面</a><br/><br/>
    <a href="logout">安全退出</a>
</h3>
</body>
</html>

dba.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <meta charset="UTF-8">
    <title>dba 页面</title>
    <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
    <link rel="stylesheet" th:href="@{css/bootstrap-theme.min.css}"/>
    <script type="text/javascript" th:src="@{js/jQuery-3.4.1.min.js}"></script>
    <script type="text/javascript" th:src="@{js/bootstrap.min.js}"></script>
</head>
<body>
<div class="panel panel-primary">
    <div class="panel-heading">
        <h3 class="panel-title">DBA 页面</h3>
    </div>
</div>
<h3>欢迎[<font color="red"><span th:text="${user}">用户名</span></font> ]访问DBA页面!
    您的权限是<font color="red"><span th:text="${role}">权限</span></font><br/><br/>
    <a href="logout">安全退出</a>
</h3>
</body>
</html>

accessDenied.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <meta charset="UTF-8">
    <title>访问拒绝页面</title>
    <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
    <link rel="stylesheet" th:href="@{css/bootstrap-theme.min.css}"/>
    <script type="text/javascript" th:src="@{js/jQuery-3.4.1.min.js}"></script>
    <script type="text/javascript" th:src="@{js/bootstrap.min.js}"></script>
</head>
<body>
<div class="panel panel-primary">
    <div class="panel-heading">
        <h3 class="panel-title">AccessDenied 页面</h3>
    </div>
</div>
<h3>欢迎[<font color="red"><span th:text="${user}">用户名</span></font> ],您没有权限访问页面!
    您的权限是<font color="red"><span th:text="${role}">权限</span></font><br/><br/>
    <a href="logout">安全退出</a>
</h3>
</body>
</html>
  1. 创建Spring Security的认证处理类
    使用自定义密码编辑器,必须实现PasswordEncoder接口。
package com.security.demo.security;

import org.springframework.security.crypto.password.PasswordEncoder;

public class MyPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence arg0, String arg1) {
        return arg1.equals(arg0.toString());
    }
}

AppSecurityConfigurer类是示例中最关键的一个类,用于处理Spring Security的用户认证和授权操作:

package com.security.demo.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class AppSecurityConfigurer extends WebSecurityConfigurerAdapter {
    /**
     * 注入认证处理类,处理不同用户跳转到不同的页面
     */
    @Autowired
    AppAuthenticatonSuccessHandler appAuthenticatonSuccessHandler;

    /**
     * 用户授权操作
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        System.out.println("AppSecurityConfigurer configure() 调用......");
        http.authorizeRequests()
                .antMatchers("/login", "/css/**", "/js/**", "/img/*").permitAll()
                .antMatchers("/", "/home").hasRole("USER")
                .antMatchers("/admin/**").hasAnyRole("ADMIN","DBA")
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
                .formLogin().loginPage("/login")
                .successHandler(appAuthenticatonSuccessHandler)
                .usernameParameter("loginname").passwordParameter("password")
                .and()
                .logout().permitAll()
                .and()
                .exceptionHandling().accessDeniedPage("/accessDenied");
    }

    /**
     * 用户认证操作
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        System.out.println("AppSecurityConfigurer configureGlobal() 调用......");
        auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder()).withUser("fkit").password("123456").roles("USER");
        auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder()).withUser("admin").password("admin").roles("ADMIN", "DBA");
    }

}
  1. 创建认证成功处理类
    AppAuthenticationSuccessHandler类继承了SimpleUrlAuthenticationSuccessHandler类,该类继承了AbstractAuthenticationTargetUrlRequestHandler父类,实现了AuthenticationSuccessHandler接口,是Spring用来处理用户认证授权并跳转到指定URL的。
package com.security.demo.security;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Component
public class AppAuthenticatonSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    // Spring Security通过RedirectStrategy对象负责所有重定向事务
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    /**
     * 重写handle方法,方法中通过RedirectStrategy对象重定向到指定的URL
     */
    @Override
    protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        // 通过determinTargetUrl方法返回需要跳转的URL
        String targetUrl = determineTargetUrl(authentication);
        // 重定向请求到指定的URL
        redirectStrategy.sendRedirect(request, response, targetUrl);
    }

    /**
     * 从Authentication对象中提取当前登录用户的角色,并根据其角色返回适当的URL
     */
    protected String determineTargetUrl(Authentication authentication) {
        String url = "";
        // 获取当前登录用户的角色权限集合
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        List<String> roles = new ArrayList<>();
        // 将角色名称添加到List集合
        for (GrantedAuthority a : authorities) {
            roles.add(a.getAuthority());
        }
        // 判断不同角色跳转到不同的URL
        if (isAdmin(roles)) {
            url = "/admin";
        } else if (isUser(roles)) {
            url = "/home";
        } else {
            url = "/accessDenied";
        }
        System.out.println("url=" + url);
        return url;
    }

    private boolean isUser(List<String> roles) {
        if (roles.contains("ROLE_USER")) {
            return true;
        }
        return false;
    }

    private boolean isAdmin(List<String> roles) {
        if (roles.contains("ROLE_ADMIN")) {
            return true;
        }
        return false;
    }

    public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
        this.redirectStrategy = redirectStrategy;
    }

    public RedirectStrategy getRedirectStrategy() {
        return redirectStrategy;
    }
}

  1. 转发请求的控制器
    AppController是一个Spring MVC的控制器,提供了响应login、home、admin、dba、accessDenied和logout请求的方法。每个方法通过getUsername()方法获得当前认证用户的用户名。通过getAuthority()方法获得当前认证用户的权限,并设置到Model当中。
    在getUsername()方法、getAuthority()方法和logoutPage()方法中都使用了Authentication对象。Authentication是一个接口,用来表示用户认证信息,在用户登录认证之后,相关信息会封装为一个Authentication具体实现类的对象,在登录成功之后又会生成一个信息更全面、包含用户权限等信息的Authentication对象,然后把它保存在SecurityContextHolder所持有的SecurityContext中,供后续的程序进行调用,如访问权限的鉴定等。
  2. 测试应用
    运行main方法启动项目,观察控制台,发现自定义类AppSecurityConfigurer的两个方法都已经被执行,说明自定义的用户认证和用户授权工作已经生效。
    用户认证和用户授权已经生效
    在浏览器中输入URL测试应用:
    登录页面
    注意:该项目存在问题。问题详情参考:https://xinzhe.blog.csdn.net/article/details/105037639
发布了279 篇原创文章 · 获赞 169 · 访问量 32万+

猜你喜欢

转载自blog.csdn.net/ARPOSPF/article/details/105022976