这方面我看相关教学视频不是太理解,于是打算写一篇加深以下理解。
用户认证和用户授权
用户认证是指系统网站对用户密码和用户ID进行校验。
用户授权是指系统网站判断该用户是否有权限和角色执行某个操作。
二者说到底都是校验操作,只是校验的对象不同而已。
用户认证的方法
用户认证一般都是在配置类或是service层做的
- 在不查询数据库里用户表的情况下,只需要在配置类设置用户ID,密码,角色和加解密就行,配置如下:
public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder)throws Exception{
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
authenticationManagerBuilder
.inMemoryAuthentication()
.withUser("root")
// 密码加密
.password(encoder.encode("123456"))
// 授予权限
.roles("manager")
.and()
.withUser("scott")
// 密码加密
.password(encoder.encode("123456"))
.roles("manager")
.and()
// 设置解密规则
.passwordEncoder(encoder);
}
- 但是是在查询数据库的情况下就不能这么做了,需要在secvice层查询表并在配置类写入对于的密码编码器即可,代码如下:
@Autowired
// 配置类
public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder)throws Exception{
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
authenticationManagerBuilder.userDetailsService(userService).passwordEncoder(encoder);
}
// Service层
@Service
public class UserService implements UserDetailsService {
@Autowired(required = false)
UserMapper userMapper;
@Override
// 重写通过用户名加载用户方法
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("===loadUserByUsername===");
QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
// 把属性username的值注入queryWrapper中
queryWrapper.eq("username", username);
// 调用sql方法,把queryWrapper注入到方法中
Users users = userMapper.selectOne(queryWrapper);
// 用户授权:设置赋予角色(ROLE_)和赋予权限(admin)
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_manager");
// 加入编译解密策略
String encode = new BCryptPasswordEncoder().encode(users.getPASSWORD());
// 返回从数据库查询的用户名字,解密的密码,以及对应的权限分装成的用户
return new User(users.getUsername(),encode,auths);
}
}
用户授权的方法
检验用户角色和权利的方法有很多,下面我会一一列举
- 首先是在service层设置用户权利,
代码如上,我就不再重复写了 - 再是启动类开启注解
//开启角色校验,以及所有校验
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
注解授权
controller层
@RequestMapping("/test")
@ResponseBody
// @Secured({"ROLE_manager","ROLE_sale"})
// 启动类开启注解,利用注解分配角色,
// 判断是否拥有某个角色,如上注解意思是只有拥有如上角色其中一个就可以访问test,但注意,只有test
// 可在Service层设置赋予角色
// @PreAuthorize("hasAuthority("")")
// @PreAuthorize("hasAnyRole("")")
// @PreAuthorize("hasAnyRole(new String[]{'',''})")
// @PreAuthorize("hasAnyAuthority('manager')")
// PreAuthorize注解综合所有角色权限注入场景,在方法执行前使用,
// 开启方法在@EnableGlobalMethodSecurity(),加入属性prePostEnabled=true即可
// @PreAuthorize("hasAnyRole(new String[]{'manager','sale'})")
// 与之相反,@PostAuthorize是方法执行后(但在return执行前)进行校验,
// 如果具有对应角色权限,执行return,否则不执行,如果为空方法没有return,依旧可行
// 同上需开启prePostEnabled=true
// 适合验证带有返回值的权限,比如返回字符串了,json之类的
// 用法和@PreAuthorize类似
// @PostAuthorize("hasAnyRole('manager1')")
public void test(){
System.out.println("===test===");
}
配置类授权
配置类
@Bean
// 设定跳转逻辑和地址放行
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((auth) -> {
try {
auth
.antMatchers("/test").hasAuthority("admin")
// 设定跳转多个权限,只要有其中一个权限就能访问
.antMatchers("/test").hasAnyAuthority(new String[]{"admin,admin1"})
// 设置角色只需拼ROLE_后面的,前面程序会自己接上
.antMatchers("/test").hasRole("manager")
// 其他所有请求都需要验证
.anyRequest()
.authenticated()
} catch (Exception e) {
throw new RuntimeException(e);
}
;
});
案例
具体案例如下:
-
项目图:
-
pom文件配置
<?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.7.14</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zh</groupId>
<artifactId>SpringSecurity</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringSecurity</name>
<description>SpringSecurity</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.42</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
- yml文件配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/shirodp
username: root
password: 123456
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: false
- bean文件
package com.zh.springsecurity.bean;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("users")
public class Users {
@TableId("userid")
private String userid;
private String username;
private String useraddr;
private String userage;
private String PASSWORD;
private String password_salt;
private String STATUS;
private String remark;
}
- mapper层
package com.zh.springsecurity.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zh.springsecurity.bean.Users;
public interface UserMapper extends BaseMapper<Users> {
}
- service层
package com.zh.springsecurity.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zh.springsecurity.bean.Users;
import com.zh.springsecurity.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import javax.management.Query;
import java.util.List;
@Service
public class UserService implements UserDetailsService {
@Autowired(required = false)
UserMapper userMapper;
@Override
// 重写通过用户名加载用户方法
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("===loadUserByUsername===");
QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
// 把属性username的值注入queryWrapper中
queryWrapper.eq("username", username);
// 调用sql方法,把queryWrapper注入到方法中
Users users = userMapper.selectOne(queryWrapper);
// 用户授权:设置赋予角色(ROLE_)和赋予权限(admin)
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_manager");
// 加入编译解密策略
String encode = new BCryptPasswordEncoder().encode(users.getPASSWORD());
// 返回从数据库查询的用户名字,解密的密码,以及对应的权限分装成的用户
return new User(users.getUsername(),encode,auths);
}
}
- controller层
package com.zh.springsecurity.controller;
import com.zh.springsecurity.bean.Users;
import jdk.internal.org.objectweb.asm.tree.analysis.Value;
import org.apache.catalina.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
@Controller
public class TestController {
@RequestMapping("/test")
@ResponseBody
// @Secured({"ROLE_manager","ROLE_sale"})
// 启动类开启注解,利用注解分配角色,
// 判断是否拥有某个角色,如上注解意思是只有拥有如上角色其中一个就可以访问test,但注意,只有test
// 可在Service层设置赋予角色
// @PreAuthorize("hasAuthority("")")
// @PreAuthorize("hasAnyRole("")")
// @PreAuthorize("hasAnyRole(new String[]{'',''})")
// @PreAuthorize("hasAnyAuthority('manager')")
// PreAuthorize注解综合所有角色权限注入场景,在方法执行前使用,
// 开启方法在@EnableGlobalMethodSecurity(),加入属性prePostEnabled=true即可
// @PreAuthorize("hasAnyRole(new String[]{'manager','sale'})")
// 与之相反,@PostAuthorize是方法执行后(但在return执行前)进行校验,
// 如果具有对应角色权限,执行return,否则不执行,如果为空方法没有return,依旧可行
// 同上需开启prePostEnabled=true
// 适合验证带有返回值的权限,比如返回字符串了,json之类的
// 用法和@PreAuthorize类似
// @PostAuthorize("hasAnyRole('manager1')")
public void test(){
System.out.println("===test===");
}
@RequestMapping("/test1")
@ResponseBody
// @PostFilter必须在return返回值是一个集合的场景下执行
// 把集合元素进行过滤,把有角色权限设置的信息留下并返回
// @PreFilter想反,只有在传入参数是集合的场景才能执行
// @PreFilter在方法执行前对集合筛选,把有角色权限的留下
@PostFilter(value = "filterObject.username == '小春'")
public List<Users> test1(){
List<Users> list = new ArrayList<>();
Users user1 = new Users("1","小春","1","1","1","1","1","1");
Users user2 = new Users("2","大春","2","2","2","2","2","2");
list.add(user1);
list.add(user2);
System.out.println("===test1===---list:"+list.toString());
return list;
}
}