十 SpringSecurity

十 SpringSecurity

git checkout -b 10.0.0_spring_security

1. 什么是 SpringSecurity

  • Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。它是保护基于 Spring 的应用程序的事实标准。
  • Spring Security 是一个专注于为 Java 应用程序提供身份验证和授权的框架。像所有 Spring 项目一样,Spring Security 的真正强大之处在于它可以轻松扩展以满足自定义需求

2. 为什么使用 Spring Security

  • 对身份验证和授权的全面且可扩展的支持
  • 防止会话固定、点击劫持、跨站点请求伪造等攻击
  • Servlet API 集成
  • 与 Spring Web MVC 的可选集成

3. 怎么使用 Spring Security

3.1 Srping Security 入门

https://github.com/jianglinChen191023/security-1

分支: git checkout -b 1.0.0_start

3.1.1 下载需要的资源文件 git clone https://github.com/spring-guides/gs-securing-web.git
  • 使用 Idea 克隆

img

3.1.2 初始化一个项目
  • Dependencies 选择Spring WebThymeleaf

img

img

3.1.3 视图

以下 Thymeleaf 模板中定义(来自src/main/resources/templates/home.html):

  • home.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Spring Security Example</title>
    </head>
    <body>
        <h1>Welcome!</h1>
        
        <p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p>
    </body>
</html>
  • hello.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Hello World!</title>
    </head>
    <body>
        <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
        <form th:action="@{/logout}" method="post">
            <input type="submit" value="Sign Out"/>
        </form>
    </body>
</html>
  • login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Spring Security Example </title>
    </head>
    <body>
        <div th:if="${param.error}">
            Invalid username and password.
        </div>
        <div th:if="${param.logout}">
            You have been logged out.
        </div>
        <form th:action="@{/login}" method="post">
            <div><label> User Name : <input type="text" name="username"/> </label></div>
            <div><label> Password: <input type="password" name="password"/> </label></div>
            <div><input type="submit" value="Sign In"/></div>
        </form>
    </body>
</html>
3.1.4 配置 SpringMVC 的类
package com.spring.security1;

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

@Configuration
public class MvcConfig implements WebMvcConfigurer {

	public void addViewControllers(ViewControllerRegistry registry) {
		registry.addViewController("/home").setViewName("home");
		registry.addViewController("/").setViewName("home");
		registry.addViewController("/hello").setViewName("hello");
		registry.addViewController("/login").setViewName("login");
	}

}
3.1.5 设置 Spring Security
  1. 添加依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-test</artifactId>
  <scope>test</scope>
</dependency>
  1. 以下安全配置(来自src/main/java/com/example/securingweb/WebSecurityConfig.java)确保只有经过身份验证的用户才能看到
package com.spring.security1;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 对请求进行授权
                .authorizeRequests()
                // 无条件访问 "/" "/home"
                .antMatchers("/", "/home").permitAll()
                // 其他请求需要登录以后才可以访问
                .anyRequest().authenticated()
                .and()
                // 以表单的形式登录
                .formLogin()
                // 指定登录页面 - 无条件访问
                // /login           GET     跳转到登录页面
                // /login           POST    提交登录表单
                // /login?error     GET     登录失败
                // /login?logout    GET     退出登录-注销功能
                .loginPage("/login")
                .permitAll()
                .and()
                // /logout          POST    302 重定向到-/login?logout 注销
                .logout()
                .permitAll();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails user =
                User.withDefaultPasswordEncoder()
                        .username("user")
                        .password("password")
                        .roles("USER")
                        .build();

        return new InMemoryUserDetailsManager(user);
    }
}
  1. 该类 WebSecurityConfig 被注释 @EnableWebSecurity 为启用 Spring Security 的 Web 安全支持并提供 Spring MVC 集成。它还扩展WebSecurityConfigurerAdapter和覆盖了它的几个方法来设置 Web 安全配置的一些细节。
  2. configure(HttpSecurity)方法定义了哪些 URL 路径应该被保护,哪些不应该。具体来说,//home路径被配置为不需要任何身份验证。所有其他路径都必须经过身份验证。
  3. 当用户成功登录时,他们将被重定向到先前请求的需要身份验证的页面。有一个自定义**/login页面(由 指定loginPage()****),每个人都可以查看。**
  4. 该userDetailsService()方法使用单个用户设置内存中的用户存储。该用户的用户名是user,密码是password,角色是USER。
3.1.6 结果
  1. 应用程序启动后,将浏览器指向http://localhost:8080. 您应该会看到主页,如下图所示:

img

  1. 当您单击该链接时,它会尝试将您带到位于 的问候语页面/hello。但是,由于该页面是安全的并且您还没有登录,它会将您带到登录页面,如下图所示:

img

  1. 在登录页面,分别输入用户名和密码字段,以测试用户身份user登录password。提交登录表单后,您将通过身份验证,然后进入欢迎页面,如下图所示:

img

  1. 如果您单击**注销**按钮,您的身份验证将被撤销,您将返回登录页面,并显示一条消息,表明您已注销。
3.1.6 Push

img

3.2 Srping Security 的访问控制

git checkout -b 2.0.0_access_control
3.2.1 请求匹配调度和授权 - 示例
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.antMatcher("/match1/**")
      .authorizeRequests()
        .antMatchers("/match1/user").hasRole("USER")
        .antMatchers("/match1/spam").hasRole("SPAM")
        .anyRequest().isAuthenticated();
  }
}
3.2.2 方法安全 - 示例
  1. 顶层配置注解开启
@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SampleSecureApplication {
}
  1. 使用 @Secured("ROLE_USER")
@Service
public class MyService {

  @Secured("ROLE_USER")
  public String secure() {
    return "Hello Security";
  }

}
  • 此示例是具有安全方法的服务。如果 Spring 创建了@Bean这种类型的 a,它会被代理,调用者必须在方法实际执行之前通过安全拦截器。如果访问被拒绝,调用者会得到一个AccessDeniedException而不是实际的方法结果。
3.2.3 授权 HTTP 请求
3.2.3.1 题目: /helllo 路径要求拥有 USER角色, /helllo_2 路径要求拥有 ADMIN角色
  • hello_2.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Hello World!</title>
    </head>
    <body>
        <h1 th:inline="text"><Hello_2></Hello_2> [[${#httpServletRequest.remoteUser}]]!</h1>
        <form th:action="@{/logout}" method="post">
            <input type="submit" value="Sign Out"/>
        </form>
    </body>
</html>
  • MvcConfig.java
package com.spring.security1;

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

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home").setViewName("home");
        registry.addViewController("/").setViewName("home");
        registry.addViewController("/hello").setViewName("hello");
        registry.addViewController("/hello_2").setViewName("hello_2");
        registry.addViewController("/login").setViewName("login");
    }

}
  • WebSecurityConfig.java
package com.spring.security1;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 对请求进行授权
                .authorizeRequests(authorize -> authorize
                        // “/hello/” URL 将被限制为具有“ROLE_USER”角色的用户。您会注意到,由于我们正在调用该hasRole方法,因此我们不需要指定“ROLE_”前缀。
                        .antMatchers("/hello").hasRole("USER")
                        .antMatchers("/hello_2").hasRole("ADMIN")
                        // 无条件访问 "/" "/home"
                        .antMatchers("/", "/home").permitAll()
                        // 其他请求需要登录以后才可以访问
                        .anyRequest().authenticated()
                )
                // 以表单的形式登录
                .formLogin(form -> form
                        // 指定登录页面 - 无条件访问
                        // /login           GET     跳转到登录页面
                        // /login           POST    提交登录表单
                        // /login?error     GET     登录失败
                        // /login?logout    GET     退出登录-注销功能
                        .loginPage("/login")
                        .permitAll()
                        .defaultSuccessUrl("/home")
                )
                // /logout          POST    302 重定向到-/login?logout 注销
                .logout();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        User.UserBuilder users = User.withDefaultPasswordEncoder();
        UserDetails user = users
                .username("user")
                .password("password")
                .roles("USER")
                .build();
        UserDetails admin = users
                .username("admin")
                .password("password")
                .roles("ADMIN")
                .build();

        return new InMemoryUserDetailsManager(user, admin);
    }

}
  • 无权限会跳转到 403 页面

img

3.2.4 Push

img

3.3 无权限403-自定义403页面

git checkout -b 3.0.0_403
3.3.1 403 页面
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>403</title>
    </head>
    <body>
        <h1 th:inline="text">403 [[${#request.getAttribute('message')}]]</h1>
    </body>
</html>
3.3.2 403 跳转
package com.spring.security1;

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

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home").setViewName("home");
        registry.addViewController("/").setViewName("home");
        registry.addViewController("/hello").setViewName("hello");
        registry.addViewController("/hello_2").setViewName("hello_2");
        registry.addViewController("/403").setViewName("403");
        registry.addViewController("/login").setViewName("login");
    }
    
}
3.3.3 配置
package com.spring.security1;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 对请求进行授权
                .authorizeRequests(authorize -> authorize
                        // “/hello/” URL 将被限制为具有“ROLE_USER”角色的用户。您会注意到,由于我们正在调用该hasRole方法,因此我们不需要指定“ROLE_”前缀。
                        .antMatchers("/hello").hasRole("USER")
                        .antMatchers("/hello_2").hasRole("ADMIN")
                        // 无条件访问 "/" "/home"
                        .antMatchers("/", "/home").permitAll()
                        // 其他请求需要登录以后才可以访问
                        .anyRequest().authenticated()
                )
                // 以表单的形式登录
                .formLogin(form -> form
                        // 指定登录页面 - 无条件访问
                        // /login           GET     跳转到登录页面
                        // /login           POST    提交登录表单
                        // /login?error     GET     登录失败
                        // /login?logout    GET     退出登录-注销功能
                        .loginPage("/login")
                        .permitAll()
                        .defaultSuccessUrl("/home")
                )
                // /logout          POST    302 重定向到-/login?logout 注销
                .logout()
                .and()
//                .exceptionHandling(handling -> handling
//                                .accessDeniedPage("/403"));
                .exceptionHandling(handling -> handling
                        .accessDeniedHandler((httpServletRequest, httpServletResponse, e) -> {
//                            httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
//                            httpServletRequest.setAttribute(WebAttributes.ACCESS_DENIED_403, e);
                            httpServletRequest.setAttribute("message", "抱歉! 您无法访问这个资源!");
                            httpServletRequest.getRequestDispatcher("/403").forward(httpServletRequest, httpServletResponse);
                        }));
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        User.UserBuilder users = User.withDefaultPasswordEncoder();
        UserDetails user = users
                .username("user")
                .password("password")
                .roles("USER")
                .build();
        UserDetails admin = users
                .username("admin")
                .password("password")
                .roles("ADMIN")
                .build();

        return new InMemoryUserDetailsManager(user, admin);
    }

}

img

3.4 记住我

git checkout -b 4.0.0_remember_me
3.4.1 内存版

程序重启需要重新登录

  • 配置
package com.spring.security1;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 对请求进行授权
                .authorizeRequests(authorize -> authorize
                        // “/hello/” URL 将被限制为具有“ROLE_USER”角色的用户。您会注意到,由于我们正在调用该hasRole方法,因此我们不需要指定“ROLE_”前缀。
                        .antMatchers("/hello").hasRole("USER")
                        .antMatchers("/hello_2").hasRole("ADMIN")
                        // 无条件访问 "/" "/home"
                        .antMatchers("/", "/home").permitAll()
                        // 其他请求需要登录以后才可以访问
                        .anyRequest().authenticated()
                )
                // 以表单的形式登录
                .formLogin(form -> form
                        // 指定登录页面 - 无条件访问
                        // /login           GET     跳转到登录页面
                        // /login           POST    提交登录表单
                        // /login?error     GET     登录失败
                        // /login?logout    GET     退出登录-注销功能
                        .loginPage("/login")
                        .permitAll()
                        .defaultSuccessUrl("/home")
                )
                // /logout          POST    302 重定向到-/login?logout 注销
                .logout()
                .and()
                /* 403 页面 */
//                .exceptionHandling(handling -> handling
//                                .accessDeniedPage("/403"));
                .exceptionHandling()
                .accessDeniedHandler((httpServletRequest, httpServletResponse, e) -> {
//                            httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
//                            httpServletRequest.setAttribute(WebAttributes.ACCESS_DENIED_403, e);
                    httpServletRequest.setAttribute("message", "抱歉! 您无法访问这个资源!");
                    httpServletRequest.getRequestDispatcher("/403").forward(httpServletRequest, httpServletResponse);
                })
                .and()
                /* 记住我 */
                .rememberMe();
//;
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        User.UserBuilder users = User.withDefaultPasswordEncoder();
        UserDetails user = users
                .username("user")
                .password("password")
                .roles("USER")
                .build();
        UserDetails admin = users
                .username("admin")
                .password("password")
                .roles("ADMIN")
                .build();

        return new InMemoryUserDetailsManager(user, admin);
    }

}
  • 表单
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Spring Security Example </title>
    </head>
    <body>
        <div th:if="${param.error}">
            Invalid username and password.
        </div>
        <div th:if="${param.logout}">
            You have been logged out.
        </div>
        <form th:action="@{/login}" method="post">
            <div><label> User Name : <input type="text" name="username"/> </label></div>
            <div><label> Password: <input type="password" name="password"/> </label></div>
            <div><label> <input type="checkbox" name="remember-me" title="记住我"/> 记住我 </label></div>
            <div><input type="submit" value="Sign In"/></div>
        </form>
    </body>
</html>
3.4.2 数据库版 - 配置数据库
3.4.2.1 相关依赖
<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.1.17</version>
		</dependency>

		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.1</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.47</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>4.3.20.RELEASE</version>
		</dependency>
3.4.2.2 配置数据库
# 配置数据库连接池
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://175.178.174.83:3306/security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
spring.datasource.username=root
# 生成的加密后的密码
spring.datasource.password=lEdXTUweRH6bqzFKEy07vHkzKrziXqkHBQNwjK6FohatpabGU+2uxc73RZftE1vD3F5GDPZDhNg6vebNTNAOcA==
# 生成的公钥
public-key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMNXQuj4GCqDt+t3ex6W9u5hf50/o/JZKS6C0lKlzzUXvWHqAhJxqWoa3C2+Jp+CUtJhugMtTpeLMf5+Acokg8ECAwEAAQ==
# 配置 connection-properties,启用加密,配置公钥。
spring.datasource.druid.connection-properties=config.decrypt=true;config.decrypt.key=${public-key}
# 启用ConfigFilter
spring.datasource.druid.filter.config.enabled=true

此处为语雀加密文本卡片,点击链接查看:https://www.yuque.com/lingchen-bf1rc/hoahc6/dr7gqc#W5fIZ

3.4.2.3 mapper
package com.spring.security1.mapper;

import com.spring.security1.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface UserMapper {

    /**
     * 根据用户名查询用户的信息
     * 
     * @param username
     * @return
     */
    @Select(value = "SELECT * FROM user WHERE username = #{username}")
    User getUserByUsername(String username);

}
3.4.2.4 entity
package com.spring.security1.entity;

public class User {

    private Integer id;
    private String username;
    private String password;

    public User() {

    }

    public User(Integer id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }

}
3.4.2.5 测试
package com.spring.security1;

import com.spring.security1.entity.User;
import com.spring.security1.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Security1ApplicationTests {

    @Autowired
    UserMapper userMapper;

    @Test
    void contextLoads() {
        User user = userMapper.getUserByUsername("123");
        System.out.println(user.getId());
    }

}
3.4.3 数据库版 - 启用令牌仓库功能
3.4.3.1 建表 - 类 JdbcTokenRepositoryImpl
  • SQL
create table persistent_logins 
(username varchar(64) not null, 
 series varchar(64) primary key,
 token varchar(64) not null, 
 last_used timestamp not null
)
3.4.3.2 配置
package com.spring.security1;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;

import javax.sql.DataSource;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    DataSource dataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);

        http
                // 对请求进行授权
                .authorizeRequests(authorize -> authorize
                        // “/hello/” URL 将被限制为具有“ROLE_USER”角色的用户。您会注意到,由于我们正在调用该hasRole方法,因此我们不需要指定“ROLE_”前缀。
                        .antMatchers("/hello").hasRole("USER")
                        .antMatchers("/hello_2").hasRole("ADMIN")
                        // 无条件访问 "/" "/home"
                        .antMatchers("/", "/home").permitAll()
                        // 其他请求需要登录以后才可以访问
                        .anyRequest().authenticated()
                )
                // 以表单的形式登录
                .formLogin(form -> form
                        // 指定登录页面 - 无条件访问
                        // /login           GET     跳转到登录页面
                        // /login           POST    提交登录表单
                        // /login?error     GET     登录失败
                        // /login?logout    GET     退出登录-注销功能
                        .loginPage("/login")
                        .permitAll()
                        .defaultSuccessUrl("/home")
                )
                // /logout          POST    302 重定向到-/login?logout 注销
                .logout()
                .and()
                /* 403 页面 */
//                .exceptionHandling(handling -> handling
//                                .accessDeniedPage("/403"));
                .exceptionHandling()
                .accessDeniedHandler((httpServletRequest, httpServletResponse, e) -> {
//                            httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
//                            httpServletRequest.setAttribute(WebAttributes.ACCESS_DENIED_403, e);
                    httpServletRequest.setAttribute("message", "抱歉! 您无法访问这个资源!");
                    httpServletRequest.getRequestDispatcher("/403").forward(httpServletRequest, httpServletResponse);
                })
                .and()
                /* 记住我 */
                .rememberMe(rememberMeConfigurer -> rememberMeConfigurer
                        .tokenRepository(jdbcTokenRepository));
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        User.UserBuilder users = User.withDefaultPasswordEncoder();
        UserDetails user = users
                .username("user")
                .password("password")
                .roles("USER")
                .build();
        UserDetails admin = users
                .username("admin")
                .password("password")
                .roles("ADMIN")
                .build();

        return new InMemoryUserDetailsManager(user, admin);
    }

}
3.4.3.3 登录 - 发现数据库中多了个数据
  • 重启程序还是登录状态

img

3.4.3.4 注销 - 发现数据库中存储的数据记录被删除了

3.5 数据库登录认证

3.5.1 了解 Srping Security 默认实现
  • 默认使用 JdbcDaoImpl类的方法查询数据库

img

  • Spring Security 的默认实现已经将 SQL 语句硬编码在 JdbcDaoImpl 类中, 这种情况下, 我们有下面三种选择
  1. 按照 JdbcDaoImpl 类中 SQL 语句设计表结构
  2. 修改 JdbcDaoImpl 类的源码
  3. 不使用 **JdbcAuthentication**()
3.5.2 自定义数据库查询方式
  • builder.userDetailsService(userDetailsService)
  • 其中 userDetailsService 需要自定义实现 UserDetailsService接口的类并自动装配
package com.spring.security1.config;

import com.spring.security1.entity.Admin;
import com.spring.security1.mapper.AdminMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    AdminMapper adminMapper;

    /**
     * 根据表单提交的用户名查询 User 对象, 并装配角色、权限等信息
     *
     * @param username 表单提交的用户名
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Admin admin = adminMapper.getAdminByLoginAcct(username);
        if (null == admin) {
            return null;
        }

        // 设置权限
        List<GrantedAuthority> authorityList = new ArrayList<>();
        authorityList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        authorityList.add(new SimpleGrantedAuthority("UPDATE"));

        String password = admin.getUserPswd();

        // AuthorityUtils权限信息
        return new User(username, "{noop}" + password, authorityList);
    }
}
3.5.2.1 Admin
  • SQL
/*
Navicat Premium Data Transfer

Source Server         : 腾讯云
Source Server Type    : MySQL
Source Server Version : 50738
Source Host           : 175.178.174.83:3306
Source Schema         : project_crowd

Target Server Type    : MySQL
Target Server Version : 50738
File Encoding         : 65001

Date: 02/08/2022 11:11:47
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_admin
-- ----------------------------
DROP TABLE IF EXISTS `t_admin`;
CREATE TABLE `t_admin` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `login_acct` varchar(255) NOT NULL,
  `user_pswd` char(32) NOT NULL,
  `user_name` varchar(255) NOT NULL,
  `email` varchar(255) NOT NULL,
  `create_time` char(19) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS = 1;
  • 实体类
package com.spring.security1.entity;

public class Admin {
    private Integer id;

    private String loginAcct;

    private String userPswd;

    private String userName;

    private String email;

    private String createTime;

    public Admin() {
    }

    public Admin(Integer id, String loginAcct, String userPswd, String userName, String email, String createTime) {
        this.id = id;
        this.loginAcct = loginAcct;
        this.userPswd = userPswd;
        this.userName = userName;
        this.email = email;
        this.createTime = createTime;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getLoginAcct() {
        return loginAcct;
    }

    public void setLoginAcct(String loginAcct) {
        this.loginAcct = loginAcct == null ? null : loginAcct.trim();
    }

    public String getUserPswd() {
        return userPswd;
    }

    public void setUserPswd(String userPswd) {
        this.userPswd = userPswd == null ? null : userPswd.trim();
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName == null ? null : userName.trim();
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email == null ? null : email.trim();
    }

    public String getCreateTime() {
        return createTime;
    }

    public void setCreateTime(String createTime) {
        this.createTime = createTime == null ? null : createTime.trim();
    }

    @Override
    public String toString() {
        return "Admin{" +
                "id=" + id +
                ", loginAcct='" + loginAcct + '\'' +
                ", userPswd='" + userPswd + '\'' +
                ", userName='" + userName + '\'' +
                ", email='" + email + '\'' +
                ", createTime='" + createTime + '\'' +
                '}';
    }
}
  • Mapper
package com.spring.security1.mapper;

import com.spring.security1.entity.Admin;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface AdminMapper {

    /**
     * 根据用户名查询用户信息
     *
     * @param loginAcct 用户名
     * @return
     */
    @Select("SELECT id, login_acct as loginAcct, user_pswd as userPswd, user_name as userName, email, create_time as createTime FROM t_admin WHERE login_acct = #{loginAcct}")
    Admin getAdminByLoginAcct(String loginAcct);

}
3.5.3 密码加密
3.5.3.1 WebSecurityConfig
package com.spring.security1;

import com.spring.security1.config.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.rememberme.JdbcTokenRepositoryImpl;

import javax.sql.DataSource;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    DataSource dataSource;
    
    @Autowired
    MyUserDetailsService myUserDetailsService;
    
    /**
    * 建立BCryptPasswordEncoder的bean
    * <p>
    * 获取方法:
    *
    * @return
    * @Autowired private PasswordEncoder passwordEncoder;
    * </p>
    */
    @Bean
    public PasswordEncoder create() {
        return new BCryptPasswordEncoder();
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        
        http
            // 对请求进行授权
            .authorizeRequests(authorize -> authorize
                               // “/hello/” URL 将被限制为具有“ROLE_USER”角色的用户。您会注意到,由于我们正在调用该hasRole方法,因此我们不需要指定“ROLE_”前缀。
                               .antMatchers("/hello").hasRole("USER")
                               .antMatchers("/hello_2").hasRole("ADMIN")
                               // 无条件访问 "/" "/home"
                               .antMatchers("/", "/home").permitAll()
                               // 其他请求需要登录以后才可以访问
                               .anyRequest().authenticated()
                              )
            // 以表单的形式登录
            .formLogin(form -> form
                       // 指定登录页面 - 无条件访问
                       // /login           GET     跳转到登录页面
                       // /login           POST    提交登录表单
                       // /login?error     GET     登录失败
                       // /login?logout    GET     退出登录-注销功能
                       .loginPage("/login")
                       .permitAll()
                       .defaultSuccessUrl("/home")
                      )
            // /logout          POST    302 重定向到-/login?logout 注销
            .logout()
            .and()
            /* 403 页面 */
            //                .exceptionHandling(handling -> handling
            //                                .accessDeniedPage("/403"));
            .exceptionHandling()
            .accessDeniedHandler((httpServletRequest, httpServletResponse, e) -> {
                //                            httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
                //                            httpServletRequest.setAttribute(WebAttributes.ACCESS_DENIED_403, e);
                httpServletRequest.setAttribute("message", "抱歉! 您无法访问这个资源!");
                httpServletRequest.getRequestDispatcher("/403").forward(httpServletRequest, httpServletResponse);
            })
            .and()
            /* 记住我 */
            .rememberMe(rememberMeConfigurer -> rememberMeConfigurer
                        .tokenRepository(jdbcTokenRepository));
    }
    
    //    @Bean
    //    @Override
    //    public UserDetailsService userDetailsService() {
    //        User.UserBuilder users = User.withDefaultPasswordEncoder();
    //        UserDetails user = users
    //                .username("user")
    //                .password("password")
    //                .roles("USER")
    //                .build();
    //        UserDetails admin = users
    //                .username("admin")
    //                .password("password")
    //                .roles("ADMIN")
    //                .build();
    //
    //        return new InMemoryUserDetailsManager(user, admin);
    //    }
    
}
3.5.3.2 MyUserDetailsService
package com.spring.security1.config;

import com.spring.security1.entity.Admin;
import com.spring.security1.mapper.AdminMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    AdminMapper adminMapper;

    @Autowired
    PasswordEncoder passwordEncoder;

    /**
     * 根据表单提交的用户名查询 User 对象, 并装配角色、权限等信息
     *
     * @param username 表单提交的用户名
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Admin admin = adminMapper.getAdminByLoginAcct(username);
        if (null == admin) {
            return null;
        }

        // 设置权限
        List<GrantedAuthority> authorityList = new ArrayList<>();
        authorityList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        authorityList.add(new SimpleGrantedAuthority("UPDATE"));

        String password = admin.getUserPswd();

        // AuthorityUtils权限信息
        return new User(username, password, authorityList);
    }
}

img

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SpringSecurity提供了自定义AuthenticationProvider和AuthenticationFilter的功能。在Spring Security中,AuthenticationProvider是一个接口,用于对用户进行身份验证。默认的实现是DaoAuthenticationProvider。你可以通过实现该接口来创建自定义的身份验证提供者,以适应特定的需求。自定义的AuthenticationProvider可以通过在配置文件中指定来替换默认的Provider。例如,在配置文件中添加以下代码可以引用自定义的Provider: ```xml <authentication-manager> <authentication-provider ref="customProvider" /> </authentication-manager> ``` 此处的`customProvider`是指自定义的AuthenticationProvider的bean的ID,你可以根据实际情况进行修改。 另外,AuthenticationFilter是用于处理身份验证请求的过滤器。它负责从请求中提取用户凭证并使用AuthenticationProvider进行身份验证。Spring Security提供了多个不同类型的AuthenticationFilter,如UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter等。你可以根据需要选择合适的AuthenticationFilter,并将其配置到Spring Security的过滤器链中。 关于Spring Security的源码,你可以在GitHub上找到它的源码存储库。在这个存储库中,你可以查看和学习Spring Security的实现细节。 总结起来,你可以通过自定义AuthenticationProvider来实现特定需求的身份验证,同时可以选择合适的AuthenticationFilter来处理身份验证请求。你可以参考Spring Security的源码来了解更多细节和实现方式。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [SpringSecurity自定义AuthenticationProvider和AuthenticationFilter](https://blog.csdn.net/weixin_34248849/article/details/93984642)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [Spring Security笔记:自定义Login/Logout Filter、AuthenticationProvider、AuthenticationToken](https://blog.csdn.net/weixin_33907511/article/details/85647330)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值