• Spring Security 基础使用


    Spring Security 是基于 Spring 应用的框架,具有功能强大且高度可定制的身份验证和访问控制的特点。

    拆箱即用

    直接在 pom.xml 文件中增加 spring-boot-starter-security 依赖即可,如下所示:

    <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">
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-securityartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-thymeleafartifactId>
                
            dependency>
        dependencies>
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-dependenciesartifactId>
                    <version>2.1.16.RELEASEversion>
                    <type>pomtype>
                    <scope>importscope>
                dependency>
            dependencies>
        dependencyManagement>
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    直接启动应用,控制台会显示其密码,如下所示:

    
    Using generated security password: be84abd1-46a4-49c2-b815-5db9c46f7007
    
    
    • 1
    • 2
    • 3

    所以,其账号就是 user ,密码就是控制台中输出的内容: be84abd1-46a4-49c2-b815-5db9c46f7007

    在配置文件中声明账号和密码

    在引入依赖后,可以直接将账号和密码进行自定义配置,如下所示:

    spring:
      security:
        user:
          name: nano
          password: nano
    
    • 1
    • 2
    • 3
    • 4
    • 5

    所以,其账号就是 nano ,密码也是: nano


    在配置类中声明账号和密码

    @Configuration
    public class SecurityConfigure extends WebSecurityConfigurerAdapter {
    
    	@Bean
    	public PasswordEncoder passwordEncoder() {
    		return new BCryptPasswordEncoder();
    	}
    
    	@Override
    	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    		// 将密码进行加密处理
    		String pwd = passwordEncoder().encode("coco");
    		auth.inMemoryAuthentication()
    				// 设置登录账号
    				.withUser("coco")
    				// 设置登录密码
    				.password(pwd).roles("管理员");
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    所以,其账号就是 coco ,密码也是: coco

    需要注意的是,如果同时存在在配置文件和配置类对账号和密码进行设置,则配置文件中的配置将会失效!


    匹配数据库中的账号和密码

    如果需要匹配数据库中的账号和密码,需要借助实现 UserDetailsService 接口来完成。

    1. 首先,准备一个数据表,如下:
    CREATE TABLE `security_user` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `username` varchar(20) NOT NULL COMMENT '登录账号',
      `password` varchar(32) NOT NULL COMMENT '登录密码',
      PRIMARY KEY (`id`),
      UNIQUE KEY `ix_username` (`username`) USING BTREE
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
    
    INSERT INTO `security_user` (`id`, `username`, `password`) VALUES (1, 'root', 'root');
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 引入相关的依赖
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <version>5.1.47version>
    dependency>
    <dependency>
        <groupId>org.mybatis.spring.bootgroupId>
        <artifactId>mybatis-spring-boot-starterartifactId>
        <version>2.2.0version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 修改配置文件,指定数据库连接信息
    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimeZone=GTM%2B8
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: root
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 编写查询密码的 Mapper 接口
    
    @Mapper
    public interface UserMapper {
    	/**
    	 * 根据账号查询密码
    	 * @param username 账号
    	 */
    	@Select("SELECT `password` FROM `security_user` WHERE `username` = #{0} LIMIT 1")
    	String selectPwdByUsername(String username);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. 编写 UserDetailsService 接口的实现类
    
    @Service
    public class UserService implements UserDetailsService {
    
    	@Resource
    	private UserMapper mapper;
    
    	@Resource
    	private PasswordEncoder encoder;
    
    	@Override
    	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    		// 根据输入的账号查询数据库中的密码
    		String pwd = mapper.selectPwdByUsername(username);
    		if (StringUtils.isEmpty(pwd)) {
    			throw new UsernameNotFoundException("用户名不存在");
    		}
    		// 将数据库中的明文秘密加密处理
    		pwd = encoder.encode(pwd);
    		// 构造授权集合对象
    		List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("管理员");
    		// 返回登录用户的对象,来自 org.springframework.security.core.userdetails.User
    		return new User(username, pwd, authorities);
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    1. 修改 Security 的配置类
    
    @Configuration
    public class SecurityConfigure extends WebSecurityConfigurerAdapter {
    
    	@Resource
    	private UserService service;
    
    	@Bean
    	public PasswordEncoder passwordEncoder() {
    		return new BCryptPasswordEncoder();
    	}
    
    	@Override
    	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    		auth.userDetailsService(service);
    	}
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    所以,其登录账号和密码就是数据表 security_userusername 字段和 password 字段的值来决定的。


    自定义登录&注销页面

    1. 修改 Security 的配置类
    @Configuration
    public class SecurityConfigure extends WebSecurityConfigurerAdapter {
    
    	@Resource
    	private UserService service;
    
    	@Bean
    	public PasswordEncoder passwordEncoder() {
    		return new BCryptPasswordEncoder();
    	}
    
    	@Override
    	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    		auth.userDetailsService(service);
    	}
    
    	@Override
    	protected void configure(HttpSecurity http) throws Exception {
    		http.logout()
    				// 自定义注销页面的路由
    				.logoutUrl("/security/logout").permitAll();
    	
    		http.formLogin()
    				// 自定义登录页面的地址
    				.loginPage("/security/login")
    				// 自定义登录处理地址,Security 会自定处理,不需要编写对应的控制器
    				// 该地址可以不用指定,默认和 loginPage 是一致的
    				.loginProcessingUrl("/security/login-processor")
    				.permitAll();
    
    		http.authorizeRequests()
    				// 特定地址不需要认证,直接允许被访问
    				.antMatchers("/favicon.ico", "/assets/**").permitAll();
    
    		http.authorizeRequests()
    				// 任何请求,都需要认证
    				.anyRequest().authenticated();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    1. 编写登录页面对应的控制器
    @Controller
    @RequestMapping("security")
    public class SecurityController {
    	/**
    	 * 自定义登录页面
    	 */
    	@GetMapping("login")
    	public String login() {
    		return "login";
    	}
    
    	/**
    	 * 自定义注销页面
    	 */
    	@GetMapping("logout")
    	public String logout(HttpServletRequest request) throws ServletException {
    		request.logout();
    		return "redirect:/";
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    1. 编写控制器对应的 ThymeLeaf 模板页面

    需要注意的是,账号输入框的 name 必须为 username,密码输入框的 name 必须为 password

    DOCTYPE html>
    <html lang="zh" xmlns:th="http://www.thymeleaf.org">
    
    <head>
        <meta charset="utf-8">
        <title>用户登录title>
        <link th:href="@{/assets/css/login.css}" rel="stylesheet">
    head>
    
    <body>
    <div class="login-container">
        <h3>用户登录h3>
        <form method="post" th:action="@{/security/login-processor }" autocomplete="off">
            <div class="form-item">
                <input type="text" name="username" placeholder="请输入登录账号"/>
            div>
            <div class="form-item">
                <input type="password" name="password" placeholder="请输入登录密码"/>
            div>
            <div class="form-item">
                <button type="submit">登录button>
            div>
        form>
        <div class="login-footer"> springboot security test div>
    div>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    到此,便完成了 Security 登录页和注销页的自定义啦。


    Ajax 提交账号和密码

    Security 在认证完成后,成功会默认执行 successHandler 中指定的 onAuthenticationSuccess 方法,失败会默认执行 failureHandler 中指定的 onAuthenticationFailure 方法,可通过重写该方法实现对异步认证的处理

    1. 编写 SecurityAuthenticationSuccessHandler 处理认证成功后的响应
    public class SecurityAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    	@Override
    	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    		if ("XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"))) {
    			// 判定为 Ajax 请求
    			response.setContentType("application/json;charset=utf-8");
    			PrintWriter writer = response.getWriter();
    			writer.write("{\"succeed\":true}");
    			writer.flush();
    			writer.close();
    			return;
    		}
    		super.onAuthenticationSuccess(request, response, authentication);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    1. 编写 SecurityAuthenticationFailureHandler 处理认证失败后的响应
    public class SecurityAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    	@Override
    	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
    		if ("XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"))) {
    			// 判定为 Ajax 请求
    			response.setContentType("application/json;charset=utf-8");
    			PrintWriter writer = response.getWriter();
    			writer.write("{\"succeed\":false}");
    			writer.flush();
    			writer.close();
    			return;
    		}
    		super.onAuthenticationFailure(request, response, exception);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    1. 修改 Security 配置类,指定认证成功和认证失败的处理器
    
    @Configuration
    public class SecurityConfigure extends WebSecurityConfigurerAdapter {
    
    	@Resource
    	private UserService service;
    
    	@Bean
    	public PasswordEncoder passwordEncoder() {
    		return new BCryptPasswordEncoder();
    	}
    
    	@Override
    	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    		auth.userDetailsService(service);
    	}
    
    	@Override
    	protected void configure(HttpSecurity http) throws Exception {
    		http.logout()
    				// 自定义注销页面的路由
    				.logoutUrl("/security/logout").permitAll();
    
    		http.formLogin()
    				// 自定义登录页面的路由
    				.loginPage("/security/login")
    				// 自定义登录处理地址,Security 会自定处理,不需要编写对应的控制器
    				// 该地址可以不用指定,默认和 loginPage 是一致的
    				.loginProcessingUrl("/security/login-processor")
    				// 指定认证成功后的处理器
    				.successHandler(new SecurityAuthenticationSuccessHandler())
    				// 指定认证失败后的处理器
    				.failureHandler(new SecurityAuthenticationFailureHandler())
    				.permitAll();
    
    		http.authorizeRequests()
    				// 特定地址不需要认证,直接允许被访问
    				.antMatchers("/favicon.ico", "/assets/**").permitAll();
    
    		http.authorizeRequests()
    				// 任何请求,都需要认证
    				.anyRequest().authenticated();
    
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    1. 修改前端的请求
    DOCTYPE html>
    <html lang="zh" xmlns:th="http://www.thymeleaf.org">
    
    <head>
        <meta charset="utf-8">
        <title>用户登录title>
        <link th:href="@{/assets/css/login.css}" rel="stylesheet">
    head>
    
    <body>
    <div class="login-container">
        <h3>用户登录h3>
        <form method="post" th:action="@{/security/login-processor }" autocomplete="off">
            <div class="form-item">
                <input type="text" name="username" placeholder="请输入登录账号"/>
            div>
            <div class="form-item">
                <input type="password" name="password" placeholder="请输入登录密码"/>
            div>
            <div class="form-item">
                <button type="button" onclick="handleSubmit()">登录button>
            div>
        form>
        <script th:src="@{/assets/js/jquery.min.js}">script>
        <script>
            function handleSubmit() {
                let f = document.forms[0];
                console.log(f.action);
                $.ajax({
                    method: 'POST',
                    url: f.action,
                    headers: {"X-Requested-With": "XMLHttpRequest"},
                    data: {
                        // security 默认开启 CSRF, 故而需要在提交信息的时候带上该参数
                        _csrf: f._csrf.value,
                        username: f.username.value,
                        password: f.password.value
                    }
                }).then(function (response) {
                    if (response && response.succeed) {
                        location.href = "/";
                    } else {
                        alert("账号或密码错误");
                        f.reset();
                    }
                });
            }
        script>
        <div class="login-footer"> springboot security test div>
    div>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
  • 相关阅读:
    [CM311-1A]-全网最全 Android 用户管理及用户应用权限
    pytorch加载的cifar10数据集,到底有没有经过归一化
    Web前端开发技术课程大作业: 关于美食的HTML网页设计——HTML+CSS+JavaScript在线美食订餐网站html模板源码30个页面:
    化学制药行业数字化供应链管理平台:精准数据采集,助力产业数字化升级
    OpenCV(二十):图像卷积
    图论模板——费用流(无法处理负环)
    Transformer时间序列预测
    [开源]多功能、高效率、低代码的前后端一体化、智能化的开发工具
    C语言进阶——指针进阶试题讲解(万字长文详解)
    从底层认识哈希表【C++】
  • 原文地址:https://blog.csdn.net/marvelousness/article/details/127991641