Spring Security快速入门
官方文档:
Spring Security :: Spring Security
功能:
-
身份认证(authentication)
-
授权(authorization)
-
防御常见攻击(protection against common attacks)
身份认证:
-
身份认证是验证
谁正在访问系统资源
,判断用户是否为合法用户。认证用户的常见方式是要求用户输入用户名和密码。
授权:
-
用户进行身份认证后,系统会控制
谁能访问哪些资源
,这个过程叫做授权。用户无法访问没有权限的资源。
防御常见攻击:
-
CSRF
-
HTTP Headers
-
HTTP Requests
1、身份认证(authentication)
官方代码示例:GitHub - spring-projects/spring-security-samples
1.1、创建Spring Boot项目
项目名:security-demo
JDK:17
SpringBoot:3.2.0(依赖了Spring Security 6.2.0)
Dependencies:Spring Web、Spring Security、Thymeleaf
1.2、创建IndexController
package com.atguigu.securitydemo.controller; @Controller public class IndexController { @GetMapping("/")public String index() {return "index";} }
1.3、创建index.html
在路径resources/templates中创建index.html
<html xmlns:th="https://www.thymeleaf.org"> <head><title>Hello Security!</title> </head> <body> <h1>Hello Security</h1> <!--通过使用@{/logout},Thymeleaf将自动处理生成正确的URL,以适应当前的上下文路径。 这样,无论应用程序部署在哪个上下文路径下,生成的URL都能正确地指向注销功能。--> <a th:href="@{/logout}">Log Out</a> </body> </html>
1.4、启动项目测试Controller
浏览器中访问:http://localhost:8080/
浏览器自动跳转到登录页面:http://localhost:8080/login
输入用户名:user
输入密码:在控制台的启动日志中查找初始的默认密码
点击"Sign in"进行登录,浏览器就跳转到了index页面
1.5、注意事项
1.5.1、@{/logout}的作用
通过使用@{/logout},Thymeleaf将自动处理生成正确的URL,以适应当前的上下文路径。这样,无论应用程序部署在哪个上下文路径下,生成的URL都能正确地指向注销功能。
例如:如果我们在配置文件中添加如下内容
server.servlet.context-path=/demo
那么@{/logout}可以自动处理url为正确的相对路径
但是如果是普通的/logout,路径就会不正确
1.5.2、页面样式无法加载的问题
页面样式bootstrap.min.css是一个CDN地址,需要通过科学上网的方式访问
否则你的登录页会加载很久,并且看到的页面是这样的(登录按钮没有样式文件渲染,但是不影响登录功能的执行)
1.6、Spring Security默认做了什么
-
保护应用程序URL,要求对应用程序的任何交互进行身份验证。
-
程序启动时生成一个默认用户“user”。
-
生成一个默认的随机密码,并将此密码记录在控制台上。
-
生成默认的登录表单和注销页面。
-
提供基于表单的登录和注销流程。
-
对于Web请求,重定向到登录页面;对于服务请求,返回401未经授权。
-
处理跨站请求伪造(CSRF)攻击。
-
处理会话劫持攻击。
-
写入Strict-Transport-Security以确保HTTPS。
-
写入X-Content-Type-Options以处理嗅探攻击。
-
写入Cache Control头来保护经过身份验证的资源。
-
写入X-Frame-Options以处理点击劫持攻击。
1.7创建项目
ide社区版本或者专业版2022-2023
服务器地址URL 阿里云脚手架 https://start.aliyun.com/
或者原来的即可
http://start.springboot.io/
Spring Web Spring Security Thymeleaf 三个选中
然后创建
IndexController
// http://127.0.0.1:8080/@GetMapping("/")public String html(){return "index.html";}
templates/ index.html
<html xmlns:th="https://www.thymeleaf.org">
<head><title>Hello Security!</title>
</head>
<body>
<h1>Hello Security</h1>
<a th:href="@{/logout}">Log Out</a>
<a href="@{/logout}">Log Out2</a>
</body>
</html>
SecurityProperties
默认情况下Spring Security将初始的用户名和密码存在了SecurityProperties类中。这个类中有一个静态内部类User,配置了默认的用户名(name = "user")和密码(password = uuid)
spring.security.user.name=user
spring.security.user.password=123
WebSecurityConfig
package com.smg.securitydemo.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;@Configuration
@EnableWebSecurity//Spring项目总需要添加此注解,SpringBoot项目中不需要
public class WebSecurityConfig {@Beanpublic UserDetailsService userDetailsService() {InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();manager.createUser( //此行设置断点可以查看创建的user对象User.withDefaultPasswordEncoder().username("huan") //自定义用户名.password("password") //自定义密码.roles("USER") //自定义角色.build());return manager;}
}
http://127.0.0.1:8080/
之前密码不可用
2、基于数据库的数据源
2.1、SQL
创建三个
- 创建数据库
CREATE DATABASE `security-demo`;
USE `security-demo`;-- 创建用户表
CREATE TABLE `user`(`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,`username` VARCHAR(50) DEFAULT NULL ,`password` VARCHAR(500) DEFAULT NULL,`enabled` BOOLEAN NOT NULL
);
-- 唯一索引
CREATE UNIQUE INDEX `user_username_uindex` ON `user`(`username`); -- 插入用户数据(密码是 "abc" )
INSERT INTO `user` (`username`, `password`, `enabled`) VALUES
('admin', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
('Helen', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
('Tom', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE);
数据库表并插入测试数据
2.2、引入依赖
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.4.1</version><exclusions><exclusion><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>3.0.3</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.10</version></dependency>
2.3、配置数据源
#MySQL数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/security-demo
spring.datasource.username=root
spring.datasource.password=123456
#SQL日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
2.4、实体类
package com.smg.securitydemo.entity;@Data
public class User {@TableId(value = "id", type = IdType.AUTO)private Integer id;private String username;private String password;private Boolean enabled;}
2.5、Mapper
package com.smg.securitydemo.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.smg.securitydemo.entity.User;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper extends BaseMapper<User> {
}
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smg.securitydemo.mapper.UserMapper"></mapper>
2.6、Service
package com.smg.securitydemo.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.smg.securitydemo.entity.User;public interface UserService extends IService<User> {
}
实现
package com.smg.securitydemo.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.smg.securitydemo.entity.User;
import com.smg.securitydemo.mapper.UserMapper;
import com.smg.securitydemo.service.UserService;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
2.7、Controller
package com.smg.securitydemo.controller;import com.smg.securitydemo.entity.User;
import com.smg.securitydemo.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
@RequestMapping("/user")
public class UserController {//localhost:8080/demo/user/list@Resourcepublic UserService userService;@GetMapping("/list")public List<User> getList(){return userService.list();}
}
登录失败
原因springboot版本太新导致不兼容
修改过后即可访问
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.4.1</version><exclusions><exclusion><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>3.0.3</version></dependency>
修改过后登录即可
3、基于数据库的用户认证
3.1、基于数据库的用户认证流程
3.2、定义DBUserDetailsManager
package com.smg.securitydemo.config;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.smg.securitydemo.entity.User;
import com.smg.securitydemo.mapper.UserMapper;
import jakarta.annotation.Resource;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.provisioning.UserDetailsManager;import java.util.ArrayList;
import java.util.Collection;public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {@Resourceprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("username", username);User user = userMapper.selectOne(queryWrapper);if (user == null) {throw new UsernameNotFoundException(username);} else {Collection<GrantedAuthority> authorities = new ArrayList<>();return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPassword(),user.getEnabled(),true, //用户账号是否过期true, //用户凭证是否过期true, //用户是否未被锁定authorities); //权限列表}}@Overridepublic UserDetails updatePassword(UserDetails user, String newPassword) {return null;}@Overridepublic void createUser(UserDetails user) {}@Overridepublic void updateUser(UserDetails user) {}@Overridepublic void deleteUser(String username) {}@Overridepublic void changePassword(String oldPassword, String newPassword) {}@Overridepublic boolean userExists(String username) {return false;}}
3.3、初始化UserDetailsService
修改WebSecurityConfig中的userDetailsService方法如下
@Beanpublic UserDetailsService userDetailsService() {DBUserDetailsManager manager = new DBUserDetailsManager();return manager;}
注意需要注释掉原来的代码
遇到这种问题
可
在
WebSecurityConfig中创建对应缺少的类
@Bean
public DBUserDetailsManager2 dbUserDetailsManager2() {// 创建DBUserDetailsManager2的实例,这里可能需要注入其他依赖,如JdbcTemplate等return new DBUserDetailsManager2();
}
登录数据库里的账号如admin
Swagger测试地址:http://localhost:8080/demo/doc.html
6、密码加密算法
参考文章
https://docs.spring.io/spring-security/reference/features/authentication/password-storage.html
6.1、密码加密方式
明文密码:
项目地址
security-demo: security-demo
oauth2-login-demo: oauth2-login-demo