文章目录
十一.SpringSecurity入门
点击跳转熟☞:【安全框架SpringSecurity进阶【详解,附有图文+示例代码】】
11.0 介绍
Spring Security 是一个功能强大、可高度定制的认证和授权框架,专为保护基于 Spring 的应用程序而设计。它提供了多种安全机制,包括但不限于用户认证、授权、密码加密、防止 CSRF 攻击、跨站请求伪造攻击(XSS)、会话管理、OAuth、LDAP 集成等。
核心概念
- 认证(Authentication):确认用户身份的过程,通常通过用户名和密码实现。Spring Security 会管理当前用户的身份信息(例如:
Authentication
对象)。 - 授权(Authorization):在认证后,判断用户是否有权访问某资源或执行某操作,通常根据角色、权限来进行控制。
Spring Security的常见功能
- 基于表单的登录:通过用户名和密码登录应用。
- 基于HTTP Basic的身份验证:通过 HTTP 请求头提供用户名和密码。
- 基于角色的授权:通过用户的角色(如
ROLE_ADMIN
,ROLE_USER
)来控制访问权限。 - 基于方法的授权:通过注解控制方法的访问权限,如
@PreAuthorize
。 - 密码加密和存储:支持各种密码加密算法(如 BCrypt、PBKDF2、SHA等)。
- 跨站请求伪造(CSRF)防护:Spring Security 内置了 CSRF 攻击防护功能。
- 会话管理:支持并发会话控制、会话固定攻击防护等。
11.1 简单认识
首先导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>
spring-boot-starter-security
</artifactId>
</dependency>
controller
@RestController
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/hello01")
public String hello(){
return "hellokwh";
}
}
默认用户名user
访问该地址,
http://localhost:8080/hello/hello01
可以修改用户名和密码:
- 在 application.properties 中进行配置
- 通过 Java 代码配置在内存中
- 通过 Java 从数据库中加载
配置文件配置用户名/密码
可以直接在 application.properties 文件中配置用户的基本信息:
spring.security.user.name=kwh
spring.security.user.password=123
11.2 Java代码配置在内存中
常规用户访问
创建配置类继承WebSecurityConfigurerAdapter
@Configuration
@EnableWebSecurity // 开启SpringSecurity的功能
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 构建认证服务,并将对象注入spring IOC容器,用户登录时,会调用该服务进行用户合法信息认证
* @return
*/
@Bean
public UserDetailsService userDetailsService(){
//从内存获取用户认证信息的服务类(了解)后期用户的信息要从表中获取
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
//构建用户,真实开发中用户信息要从数据库加载构建
UserDetails u1 = User
.withUsername("itcast")
//{noop}:no operration--》表示登录时对避免不做任何操作,
//说白了就是明文比对
.password("{noop}123456")
.authorities("P5", "ROLE_ADMIN")//用户的权限信息
.build();
UserDetails u2 = User
.withUsername("ithz")
.password("{noop}123456")
//如果角色也作为一种权限资源,则角色名称的前缀必须加ROLE_
.authorities("P7",
"ROLE_SELLER","ROLE_ADMIN")
.build();
inMemoryUserDetailsManager.createUser(u1);
inMemoryUserDetailsManager.createUser(u2);
return inMemoryUserDetailsManager;
}
}
controller
@RestController
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/hello01")
public String hello(){
return "hellokwh";
}
}
再次访问
http://localhost:8080/hello/hello01
只有用户itcast和ithz用户才能登录
用户分配权限
编码方式
controller
@RestController
@ResponseBody
public class HelloController {
//@PreAuthorize("hasAuthority('P5')")
@GetMapping("/hello")
public String hello(){
return "hello world today";
}
//@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/ss01")
public String springSecurity01(){
return "springSecurity01";
}
// @PermitAll
// @PreAuthorize("permitAll()")
@GetMapping("/register")
public String springSecurity02(){
return "register.....";
}
}
配置类
@Configuration
@EnableWebSecurity // 开启SpringSecurity的功能
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 构建认证服务,并将对象注入spring IOC容器,用户登录时,会调用该服务进行用户合法信息认证
* @return
*/
@Bean
public UserDetailsService userDetailsService(){
//从内存获取用户认证信息的服务类(了解)后期用户的信息要从表中获取
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
//构建用户,真实开发中用户信息要从数据库加载构建
UserDetails u1 = User
.withUsername("itcast")
//{noop}:no operration--》表示登录时对避免不做任何操作,说白了就是明文比对
.password("{noop}123456")
//用户的权限信息
.authorities("P5", "ROLE_ADMIN")
.build();
UserDetails u2 = User
.withUsername("ithz")
.password("{noop}123456")
//如果角色也作为一种权限资源,则角色名称的前缀必须加ROLE_
.authorities("P7",
"ROLE_SELLER","ROLE_ADMIN")
.build();
inMemoryUserDetailsManager.createUser(u1);
inMemoryUserDetailsManager.createUser(u2);
return inMemoryUserDetailsManager;
}
// 配置资源权限绑定设置
@Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http);
http.formLogin()
.and()
.logout()//登出用默认的路径登出 /logout
.permitAll()//允许所有的用户访问登录或者登出的路径
.and()
.csrf().disable()//启用CSRF,防止CSRF攻击
.authorizeRequests()//授权方法,该方法后有若干子方法进行不同的授权规则处理
//允许所有账户都可访问(不登录即可访问),同时可指定多个路径
.antMatchers("/register").permitAll()//注册,允许所有人访问该路径
.antMatchers("/hello").hasAuthority("P5")
.antMatchers("/ss01").hasRole("ADMIN")//拥有ROLE_ADMIN角色的人才可以访问; 底层将role加ROLE_前缀转化成权限标识
.anyRequest().authenticated();//除了上边配置的请求资源,其它资源都必须授权才能访问
}
}
注解方式
controller
@RestController
@ResponseBody
public class HelloController {
@PreAuthorize("hasAuthority('P5')")
@GetMapping("/hello")
public String hello(){
return "hello world today";
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/ss01")
public String springSecurity01(){
return "springSecurity01";
}
@PermitAll
// @PreAuthorize("permitAll()")
@GetMapping("/register")
public String springSecurity02(){
return "register.....";
}
}
【配置类】
@Configuration
@EnableWebSecurity // 开启SpringSecurity的功能
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启SpringSecurity注解支持
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 构建认证服务,并将对象注入spring IOC容器,用户登录时,会调用该服务进行用户合法信息认证
* @return
*/
@Bean
public UserDetailsService userDetailsService(){
//从内存获取用户认证信息的服务类(了解)后期用户的信息要从表中获取
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
//构建用户,真实开发中用户信息要从数据库加载构建
UserDetails u1 = User
.withUsername("itcast")
.password("{noop}123456")//{noop}:no operration--》表示登录时对避免不做任何操作,说白了就是明文比对
.authorities("P5", "ROLE_ADMIN")//用户的权限信息
.build();
UserDetails u2 = User
.withUsername("ithz")
.password("{noop}123456")
//如果角色也作为一种权限资源,则角色名称的前缀必须加ROLE_
.authorities("P7",
"ROLE_SELLER","ROLE_ADMIN")
.build();
inMemoryUserDetailsManager.createUser(u1);
inMemoryUserDetailsManager.createUser(u2);
return inMemoryUserDetailsManager;
}
// 配置资源权限绑定设置
@Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http);
http.formLogin()
.and()
.logout()//登出用默认的路径登出 /logout
.permitAll()//允许所有的用户访问登录或者登出的路径
.and()
.csrf().disable()//启用CSRF,防止CSRF攻击
.authorizeRequests();//授权方法,该方法后有若干子方法进行不同的授权规则处理
// //允许所有账户都可访问(不登录即可访问),同时可指定多个路径
// .antMatchers("/register").permitAll()//注册,允许所有人访问该路径
// .antMatchers("/hello").hasAuthority("P5")
// .antMatchers("/ss01").hasRole("ADMIN")//拥有ROLE_ADMIN角色的人才可以访问; 底层将role加ROLE_前缀转化成权限标识
// .anyRequest().authenticated();//除了上边配置的请求资源,其它资源都必须授权才能访问
}
}
11.3 数据库中获取用户名和密码
第一种方式
首先在resources下的static包下创建登录表单login.html
<form action="/formlogin" method="post">
用户名:
<input type="text" name="username" ></br></input>
密码:
<input type="password" name="password" ></br></input>
<input type="submit" value="登录">
</form>
配置文件:(用Mybatis-Plus)
server:
port: 80
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/smbms?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
username: root
password: sa123456
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: false
mapper-locations: classpath:mapper/*xml
【数据库】
DROP TABLE IF EXISTS `smbms_user`;
CREATE TABLE `smbms_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`userCode` varchar(15) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NULL DEFAULT NULL COMMENT '用户编码',
`userName` varchar(15) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NULL DEFAULT NULL COMMENT '用户名称',
`userPassword` varchar(15) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NULL DEFAULT NULL COMMENT '用户密码',
`gender` int NULL DEFAULT NULL COMMENT '性别(1:女、 2:男)',
`birthday` date NULL DEFAULT NULL COMMENT '出生日期',
`phone` varchar(15) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NULL DEFAULT NULL COMMENT '手机',
`address` varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NULL DEFAULT NULL COMMENT '地址',
`userRole` bigint NULL DEFAULT NULL COMMENT '用户角色(取自角色表-角色id)',
`createdBy` bigint NULL DEFAULT NULL COMMENT '创建者(userId)',
`creationDate` datetime NULL DEFAULT NULL COMMENT '创建时间',
`modifyBy` bigint NULL DEFAULT NULL COMMENT '更新者(userId)',
`modifyDate` datetime NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_unicode_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
【实体类】
@Data
@ToString
@TableName("smbms_user")
public class User {
private Integer id; //id
private String userCode; //用户编码
private String userName; //用户名称
private String userPassword; //用户密码
private Integer gender; //性别
private Date birthday; //出生日期
private String phone; //电话
private String address; //地址
private Integer userRole; //用户角色
private Integer createdBy; //创建者
private Date creationDate; //创建时间
private Integer modifyBy; //更新者
private Date modifyDate; //更新时间
}
【mapper】
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
创建LoginAuthenticationProvider类,实现接口AuthenticationProvider
// 自定义认证
@Component
public class LoginAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserMapper userMapper;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 获取表单提交的用户名和密码
String userName = (String) authentication.getPrincipal(); //拿到username
String password = (String) authentication.getCredentials(); //拿到password
//调用数据库进行校验
User user = userMapper.selectOne(new QueryWrapper<User>()
.eq("userCode", userName)
.eq("userPassword", password));
// 登录成功
if (user != null) {
// 权限集合
// 认证登录对象
// UsernamePasswordAuthenticationToken roleUser = new UsernamePasswordAuthenticationToken(
// user,
// user.getUserPassword(),
// AuthorityUtils
// .commaSeparatedStringToAuthorityList("ROLE_USER"));
// return roleUser;
// 权限集合
List<GrantedAuthority> authorities = AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_USER");
UsernamePasswordAuthenticationToken roleUser =
new UsernamePasswordAuthenticationToken(
user,
user.getUserPassword(),
authorities);
return roleUser;
}
// 登录失败返回 null
return null;
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private LoginAuthenticationProvider loginAuthenticationProvider;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// super.configure(auth);
//自定义认证,替换认证的拦截器
auth.authenticationProvider(loginAuthenticationProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http);
// 关闭csrf防护
http.csrf().disable()
.headers()
.frameOptions().disable();
//认证配置
http.formLogin()
//登录页面
.loginPage("/login.html")
.loginProcessingUrl("/formlogin")//表单提交的路径
.defaultSuccessUrl("/index/info")//登录成功跳转路径
.usernameParameter("username")//表单中用户名字段
.passwordParameter("password")//表单中密码字段
.failureUrl("/loginout.html")//登录失败跳转路径
.permitAll();//允许所有用户访问
// 权限配置
http.authorizeRequests()
.antMatchers("/").permitAll()//允许所有用户访问
.antMatchers("/index/**").hasAnyRole("USER","ADMIN")
.antMatchers("/user/**").hasRole("USER")
//除了上面的资源,其他请求需要认证
.antMatchers("/admin/**").hasRole("ADMIN");
}
}
controller
@RestController
@RequestMapping("/admin")
public class AdminController {
@GetMapping("/info")
public String adminInfo(){
return "admin info。。。。。";
}
}
============================================
@RestController
@RequestMapping("/index")
public class IndexController {
@GetMapping("/info")
public String index() {
return "这是首页...";
}
}
===================================================
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/info")
public String userinfo(){
return "userInfo....";
}
}
访问http://localhost:80/index/info,进入自己定义的页面login.html
第二种方式
【数据库】
create database security_demo default charset=utf8mb4;
use security_demo;
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(100) DEFAULT NULL,
`password` varchar(100) DEFAULT NULL,
`roles` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `tb_user` VALUES (1, 'itcast', '$2a$10$f43iK9zKD9unmgLao1jqI.VluZ.Rr/XijizVEA73HeOu9xswaUBXC', 'ROLE_ADMIN,P5');
INSERT INTO `tb_user` VALUES (2, 'itheima', '$2a$10$f43iK9zKD9unmgLao1jqI.VluZ.Rr/XijizVEA73HeOu9xswaUBXC', 'ROLE_SELLER,P7,ROLE_ADMIN');
【pom.xml】
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
【application.properties】
server.port=8080
#spring.security.user.name=admin
#spring.security.user.password=admin
mybatis.mapper-locations=classpath:mapper/*xml
mybatis.type-aliases-package=com.ithz.pojo
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/security_demo?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
spring.datasource.username=root
spring.datasource.password=sa123456
【实体类】
@Data
@NoArgsConstructor
@AllArgsConstructor
// @Builder
public class TbUser {
private Integer id;
private String username;
private String password;
private String roles;
private static final long serialVersionUID = 1L;
}
【mapper】
@Mapper
public interface TbUserMapper {
//根据用户名称查询用户信息
TbUser findByUserName(@Param("userName") String userName);
}
【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.ithz.mapper.TbUserMapper">
<select id="findByUserName" resultType="com.ithz.pojo.TbUser">
select id,username,password,roles from tb_user where username = #{userName}
</select>
</mapper>
【自定义UserDetailsService】
public interface MyUserDetailsService {
//根据用户名称查询用户信息
TbUser findByUserName(@Param("userName") String userName);
}
/**
* 定义用户认证时获取认证用户详情信息服务的bean
*/
@Service
public class MyUserDetailsServiceImpl implements MyUserDetailsService, UserDetailsService {
@Autowired
private TbUserMapper tbUserMapper;
@Override
public TbUser findByUserName(String userName) {
return null;
}
/**
* 根据认证时传入的用户名去指定资源库下获取详情信息:用户名、密码密文、权限对象集合
* @param username the username identifying the user whose data is required.
* @return
* @throws UsernameNotFoundException
*/
// UserDetailsService
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
TbUser dbUser = tbUserMapper.findByUserName(username);
if (dbUser == null) {
throw new UsernameNotFoundException("当前用户不存在");
}
// 组装用户详情信息:用户名、密码密文、权限对象集合
List<GrantedAuthority> authorities = AuthorityUtils
.commaSeparatedStringToAuthorityList(dbUser.getRoles());
UserDetails userDetails = User.builder()
.username(dbUser.getUsername())// 用户名
.password(dbUser.getPassword()) //密码密文
.authorities(authorities)// 权限对象集合
.build();
return userDetails;
}
}
【在配置类 SecurityConfig中配置Bean: 】
@Configuration
@EnableWebSecurity // 开启SpringSecurity的功能
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启SpringSecurity注解支持
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 构建认证服务,并将对象注入spring IOC容器,用户登录时,会调用该服务进行用户合法信息认证
* @return
*/
// @Bean
// public UserDetailsService userDetailsService(){
// //从内存获取用户认证信息的服务类(了解)后期用户的信息要从表中获取
// InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
// //构建用户,真实开发中用户信息要从数据库加载构建
// UserDetails u1 = User
// .withUsername("itcast")
// .password("{noop}123456")//{noop}:no operration--》表示登录时对避免不做任何操作,说白了就是明文比对
// .authorities("P5", "ROLE_ADMIN")//用户的权限信息
// .build();
//
// UserDetails u2 = User
// .withUsername("ithz")
// .password("{noop}123456")
// //如果角色也作为一种权限资源,则角色名称的前缀必须加ROLE_
// .authorities("P7", "ROLE_SELLER","ROLE_ADMIN")
// .build();
//
// inMemoryUserDetailsManager.createUser(u1);
// inMemoryUserDetailsManager.createUser(u2);
// return inMemoryUserDetailsManager;
// }
// 配置资源权限绑定设置
@Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http);
http.formLogin()
.and()
.logout()//登出用默认的路径登出 /logout
.permitAll()//允许所有的用户访问登录或者登出的路径
.and()
.csrf().disable()//启用CSRF,防止CSRF攻击
.authorizeRequests();//授权方法,该方法后有若干子方法进行不同的授权规则处理
// //允许所有账户都可访问(不登录即可访问),同时可指定多个路径
// .antMatchers("/register").permitAll()//注册,允许所有人访问该路径
// .antMatchers("/hello").hasAuthority("P5")
// .antMatchers("/ss01").hasRole("ADMIN")//拥有ROLE_ADMIN角色的人才可以访问; 底层将role加ROLE_前缀转化成权限标识
// .anyRequest().authenticated();//除了上边配置的请求资源,其它资源都必须授权才能访问
}
/**
* 配置密码加密方式
* @return
*/
@Bean
public PasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
注意:
1.User是UserDetails接口的实现类,封装了用户权限相关的的数据及用户的权限数据, 不要导错包 ;
2.工程已经配置好了BCryptPasswordEncoder加密相关bean,底层会自动调用;
【controller】
@RestController
@ResponseBody
public class HelloController {
@PreAuthorize("hasAuthority('P5')")
@GetMapping("/hello")
public String hello(){
return "hello world today";
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/ss01")
public String springSecurity01(){
return "springSecurity01";
}
@PermitAll
// @PreAuthorize("permitAll()")
@GetMapping("/register")
public String springSecurity02(){
return "register.....";
}
}