概述
SpringSecurity基于Spring框架,提供了一套Web应用安全性的完整解决方案,核心功能包括:用户认证、用户授权;
- 用户认证:
验证某个用户是否为系统中的合法主体,用户能否访问该系统。 - 用户授权:验证某个用户是否有权限执行某个操作。
SpringSecurity的特点:
- 和Spring无缝整合
- 全面的权限控制
- 专门为web开发而设计
1.旧版本不能脱离Web环境使用;
2.新版本对整个框架进行了分层抽取,分成了核心模块和Web模块,单独引入核心木块就可以脱离Web环境; - 重量级
**Shiro的特点:
- 轻量级:Shiro主张把复杂问题简单化,针对性能有更高要求的互联网应用有更好表现;
- 通用性:
1.好处:不局限于Web环境,可以头里Web环境使用
2.缺陷:在Web环境下的一些特定的需求需要手动编写代码定制
HelloWorld
依赖
<?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>
<groupId>per.xgt</groupId>
<artifactId>SpringSecurity-01-HelloWorld</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringSecurity-01-HelloWorld</name>
<description>Demo project for Spring Boot</description>
<packaging>jar</packaging>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.2.1.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>per.xgt.SpringSecurity01HelloWorldApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
写一个Controller
package per.xgt.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: gentao9527
* @date: 2022/8/7 12:04
* @Description: TODO
* @Version: 1.0
*/
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping("/hello")
public String add(){
return "hello";
}
}
访问接口
访问接口:http://localhost:8081/login
自动跳转到如下页面
默认登录用户名和密码:
默认的用户名是user
默认密码在控制台,如下:
用户认证
设置登录的用户名和密码
通过配置文件
# 应用名称
spring.application.name=SpringSecurity-01-HelloWorld
server.port=8081
spring.security.user.name=admin
spring.security.user.password=123456
通过配置类
package per.xgt.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @Author: gentao9527
* @date: 2022/8/7 14:15
* @Description: TODO
* @Version: 1.0
*/
@Configuration
public class SecutiryConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 密码加密
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String password = passwordEncoder.encode("123456");
// 设置账号 密码 角色
auth.inMemoryAuthentication().withUser("user").password(password).roles("admin");
}
/**
* 注入 PasswordEncoder Bean
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
自定义编写实现类
- 配置类
package per.xgt.config;
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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @Author: gentao9527
* @date: 2022/8/7 15:52
* @Description: TODO
* @Version: 1.0
*/
@Configuration
public class SecurityMyConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
public PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
- userDetailsService的实现类
package per.xgt.service;
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 java.util.List;
/**
* @Author: gentao9527
* @date: 2022/8/7 15:56
* @Description: TODO
* @Version: 1.0
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 权限集合
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("test");
// 用户认证
return new User("xgt",new BCryptPasswordEncoder().encode("123456"),authorities);
}
}
查询数据库的用户认证
- 依赖
<?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>
<groupId>per.xgt</groupId>
<artifactId>SpringSecurity-01-HelloWorld</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringSecurity-01-HelloWorld</name>
<description>Demo project for Spring Boot</description>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.2.1.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>per.xgt.SpringSecurity01HelloWorldApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- 实体类
package per.xgt.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @Author: gentao9527
* @date: 2022/8/7 16:21
* @Description: TODO
* @Version: 1.0
*/
@Data
@TableName("users")
public class User {
@TableId(value = "id",type = IdType.AUTO)
private Integer id;
@TableField(value = "userName")
private String userName;
@TableField(value = "password")
private String password;
}
- Mapper接口
package per.xgt.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import per.xgt.entity.User;
/**
* @author Valen
* @version V1.0
* @date 2022/8/7 16:27
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
- 在UserDetailsService实现类中用mapper获取账号密码
package per.xgt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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 per.xgt.mapper.UserMapper;
import java.util.List;
/**
* @Author: gentao9527
* @date: 2022/8/7 15:56
* @Description: TODO
* @Version: 1.0
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
/**
* @param s 用户名
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 调用mapper根据用户名查询数据库
QueryWrapper<per.xgt.entity.User> wrapper = new QueryWrapper<>();
wrapper.eq("userName",s);
per.xgt.entity.User user = userMapper.selectOne(wrapper);
if (null == user){
// 没用这个用户
throw new UsernameNotFoundException("用户不存在!");
}
// 权限集合
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("test");
// 得到用户名和密码,返回
return new User(user.getUserName(),new BCryptPasswordEncoder().encode(user.getPassword()),authorities);
}
}
- 配置Mapper扫描
package per.xgt;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "per.xgt.mapper")
public class SpringSecurity01HelloWorldApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecurity01HelloWorldApplication.class, args);
}
}
自定义登录页面
- 配置
package per.xgt.config;
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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @Author: gentao9527
* @date: 2022/8/7 15:52
* @Description: TODO
* @Version: 1.0
*/
@Configuration
public class SecurityMyConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
public PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
/**
* 配置自定义用户登录页面
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义自己编写的登录页面
http.formLogin()
// 登录页面的地址
.loginPage("/login.html")
// 登录访问路径
.loginProcessingUrl("/test/login")
// 登录成功后访问的路径
.defaultSuccessUrl("/test/index").permitAll()
.and().authorizeRequests()
// 设置不需要认证可以访问的路径
.antMatchers("/","/test/hello","/test/login").permitAll()
.anyRequest().authenticated()
// 关闭CSRF防护
.and().csrf().disable();
}
}
- 相关页面
表单提交的账户名必须为username,密码必须为password;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/test/login" method="post">
<input type="text" name="username">
<br>
<input type="password" name="password">
<br>
<input type="submit" value="登录">
</form>
</body>
</html>
- 引入thymeleaf
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- controller类
package per.xgt.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @Author: gentao9527
* @date: 2022/8/7 12:04
* @Description: TODO
* @Version: 1.0
*/
@Controller
@RequestMapping("/test")
public class TestController {
@RequestMapping("/hello")
@ResponseBody
public String add(){
return "hello";
}
@RequestMapping("/index")
public String index(){
return "index";
}
}
用户授权
基于权限访问控制
单个权限hasAuthority
如果当前的主题具有指定的权限则返回true,否则返回false
修改配置类
package per.xgt.config;
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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @Author: gentao9527
* @date: 2022/8/7 15:52
* @Description: TODO
* @Version: 1.0
*/
@Configuration
public class SecurityMyConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
public PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
/**
* 配置自定义用户登录页面
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义自己编写的登录页面
http.formLogin()
// 登录页面的地址
.loginPage("/login.html")
// 登录访问路径
.loginProcessingUrl("/test/login")
// 登录成功后访问的路径
.defaultSuccessUrl("/test/index").permitAll()
.and().authorizeRequests()
// 设置不需要认证可以访问的路径
.antMatchers("/","/test/hello","/test/login").permitAll()
// 当前用户只有具有admin权限才可以访问这个路径
.antMatchers("/test/index").hasAuthority("admin")
.anyRequest().authenticated()
// 关闭CSRF防护
.and().csrf().disable();
}
}
package per.xgt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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 per.xgt.mapper.UserMapper;
import java.util.List;
/**
* @Author: gentao9527
* @date: 2022/8/7 15:56
* @Description: TODO
* @Version: 1.0
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
/**
* @param s 用户名
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 调用mapper根据用户名查询数据库
QueryWrapper<per.xgt.entity.User> wrapper = new QueryWrapper<>();
wrapper.eq("userName",s);
per.xgt.entity.User user = userMapper.selectOne(wrapper);
if (null == user){
// 没用这个用户
throw new UsernameNotFoundException("用户不存在!");
}
// 权限集合
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
// 得到用户名和密码,返回
return new User(user.getUserName(),new BCryptPasswordEncoder().encode(user.getPassword()),authorities);
}
}
多个权限hasAnyAuthority
修改配置类
package per.xgt.config;
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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @Author: gentao9527
* @date: 2022/8/7 15:52
* @Description: TODO
* @Version: 1.0
*/
@Configuration
public class SecurityMyConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
public PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
/**
* 配置自定义用户登录页面
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义自己编写的登录页面
http.formLogin()
// 登录页面的地址
.loginPage("/login.html")
// 登录访问路径
.loginProcessingUrl("/test/login")
// 登录成功后访问的路径
.defaultSuccessUrl("/test/index").permitAll()
.and().authorizeRequests()
// 设置不需要认证可以访问的路径
.antMatchers("/","/test/hello","/test/login").permitAll()
// 当前用户只有具有admin权限才可以访问这个路径
// .antMatchers("/test/index").hasAuthority("admin")
.antMatchers("/test/index").hasAnyAuthority("admin","manager")
.anyRequest().authenticated()
// 关闭CSRF防护
.and().csrf().disable();
}
}
基于角色访问控制
如果用户具备给定角色就返回true:允许访问,否则出现403
基于单个角色访问控制hasRole
修改配置类
package per.xgt.config;
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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @Author: gentao9527
* @date: 2022/8/7 15:52
* @Description: TODO
* @Version: 1.0
*/
@Configuration
public class SecurityMyConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
public PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
/**
* 配置自定义用户登录页面
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义自己编写的登录页面
http.formLogin()
// 登录页面的地址
.loginPage("/login.html")
// 登录访问路径
.loginProcessingUrl("/test/login")
// 登录成功后访问的路径
.defaultSuccessUrl("/test/index").permitAll()
.and().authorizeRequests()
// 设置不需要认证可以访问的路径
.antMatchers("/","/test/hello","/test/login").permitAll()
// 当前用户只有具有admin权限才可以访问这个路径
// .antMatchers("/test/index").hasAuthority("admin")
// .antMatchers("/test/index").hasAnyAuthority("admin","manager")
// 当前角色只有是admin角色才可以访问
.antMatchers("/test/index").hasRole("admin")
.anyRequest().authenticated()
// 关闭CSRF防护
.and().csrf().disable();
}
}
package per.xgt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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 per.xgt.mapper.UserMapper;
import java.util.List;
/**
* @Author: gentao9527
* @date: 2022/8/7 15:56
* @Description: TODO
* @Version: 1.0
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
/**
* @param s 用户名
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 调用mapper根据用户名查询数据库
QueryWrapper<per.xgt.entity.User> wrapper = new QueryWrapper<>();
wrapper.eq("userName",s);
per.xgt.entity.User user = userMapper.selectOne(wrapper);
if (null == user){
// 没用这个用户
throw new UsernameNotFoundException("用户不存在!");
}
// 权限集合
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("manager,ROLE_admin");
// 得到用户名和密码,返回
return new User(user.getUserName(),new BCryptPasswordEncoder().encode(user.getPassword()),authorities);
}
}
在验证是否具有当前角色的时候,String会自动在前面拼接一个"ROLE_",所以存储用户角色的时候可以加上前缀
基于多个角色访问控制hasAnyRole
修改配置
表示用户用户具备任何一个条件都可以访问
package per.xgt.config;
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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @Author: gentao9527
* @date: 2022/8/7 15:52
* @Description: TODO
* @Version: 1.0
*/
@Configuration
public class SecurityMyConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
public PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
/**
* 配置自定义用户登录页面
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义自己编写的登录页面
http.formLogin()
// 登录页面的地址
.loginPage("/login.html")
// 登录访问路径
.loginProcessingUrl("/test/login")
// 登录成功后访问的路径
.defaultSuccessUrl("/test/index").permitAll()
.and().authorizeRequests()
// 设置不需要认证可以访问的路径
.antMatchers("/","/test/hello","/test/login").permitAll()
// 当前用户只有具有admin权限才可以访问这个路径
// .antMatchers("/test/index").hasAuthority("admin")
// .antMatchers("/test/index").hasAnyAuthority("admin","manager")
// 当前角色只有是admin角色才可以访问
// .antMatchers("/test/index").hasRole("admin")
.antMatchers("/test/index").hasAnyRole("admin,manager")
.anyRequest().authenticated()
// 关闭CSRF防护
.and().csrf().disable();
}
}
自定义403页面
修改配置类
配置没有权限返回跳转到指定的自定义的页面
package per.xgt.config;
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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @Author: gentao9527
* @date: 2022/8/7 15:52
* @Description: TODO
* @Version: 1.0
*/
@Configuration
public class SecurityMyConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
public PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
/**
* 配置自定义用户登录页面
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 配置没有权限访问跳转到自己自定义的页面
http.exceptionHandling().accessDeniedPage("/error/unAuth.html");
// 自定义自己编写的登录页面
http.formLogin()
// 登录页面的地址
.loginPage("/login.html")
// 登录访问路径
.loginProcessingUrl("/test/login")
// 登录成功后访问的路径
.defaultSuccessUrl("/test/index").permitAll()
.and().authorizeRequests()
// 设置不需要认证可以访问的路径
.antMatchers("/","/test/hello","/test/login").permitAll()
// 当前用户只有具有admin权限才可以访问这个路径
// .antMatchers("/test/index").hasAuthority("admin")
// .antMatchers("/test/index").hasAnyAuthority("admin","manager")
// 当前角色只有是admin角色才可以访问
// .antMatchers("/test/index").hasRole("admin")
.antMatchers("/test/index").hasAnyRole("admin,manager")
.anyRequest().authenticated()
// 关闭CSRF防护
.and().csrf().disable();
}
}
错误页面必须以 “/” 开始
注解使用
开启注解功能
将 @EnableGlobalMethodSecurity(securedEnabled = true) 放在启动了或者配置类上
@Secured
在controller的方法上使用注解 @Secured :用户具有某个角色,可以访问方法
@RequestMapping("/update")
@ResponseBody
@Secured({"ROLE_haha","ROLE_hehe"})
public String update(){
return "index";
}
@PreAuthorize
适合进入方法前的权限验证,可以将登录用户的roles/permissions参数传到方法中
需要开启相关功能
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
在Controller方法上添加注解
@RequestMapping("/update")
@ResponseBody
@PreAuthorize("hasAnyRole('admin')")
public String update(){
return "index";
}
@PostAuthorize
在方法执行后再进行权限验证,适合验证带有返回值的权限
需要开启相关功能
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
在方法上添加注解
@RequestMapping("/update")
@ResponseBody
@PostAuthorize("hasAnyAuthority('admins')")
public String update(){
System.out.println("update......");
return "index";
}
即使没有权限访问,方法也执行了
@PreFilter
传入方法数据进行过滤
@RequestMapping("update")
@PreAuthorize("hasRole('ROLE_admin')")
@PreFilter(value = "filterObject.id%2==0")
@ResponseBody
public List<User> update(@RequestBody List<User>
list){
list.forEach(t-> {
System.out.println(t.getId()+"\t"+t.getUserName());
});
return list;
}
@PostFilter
方法返回数据进行过滤
@RequestMapping("/update")
@ResponseBody
@PostAuthorize("hasAnyAuthority('admin')")
@PostFilter(value = "filterObject.userName == admin")
public List<User> update(){
List list = new ArrayList<User>();
list.add(new User(1,"xgt","xgt"));
list.add(new User(2,"admin","admin"));
return list;
}
注销
/**
* 配置自定义用户登录页面
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 退出登录
http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
// 配置没有权限访问跳转到自己自定义的页面
http.exceptionHandling().accessDeniedPage("/error/unAuth.html");
// 自定义自己编写的登录页面
http.formLogin()
// 登录页面的地址
.loginPage("/login.html")
// 登录访问路径
.loginProcessingUrl("/test/login")
// 登录成功后访问的路径
.defaultSuccessUrl("/test/index").permitAll()
.and().authorizeRequests()
// 设置不需要认证可以访问的路径
.antMatchers("/","/test/hello","/test/login").permitAll()
// 当前用户只有具有admin权限才可以访问这个路径
// .antMatchers("/test/index").hasAuthority("admin")
// .antMatchers("/test/index").hasAnyAuthority("admin","manager")
// 当前角色只有是admin角色才可以访问
// .antMatchers("/test/index").hasRole("admin")
.antMatchers("/test/index").hasAnyRole("admin,manager")
.anyRequest().authenticated()
// 关闭CSRF防护
.and().csrf().disable();
}
自动登录
相关表结构
CREATE TABLE `persistent_logins` (
`username` varchar(64) NOT NULL,
`series` varchar(64) NOT NULL,
`token` varchar(64) NOT NULL,
`last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
CURRENT_TIMESTAMP,
PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
配置类相关内容
注册相关数据库操作对象 注入数据源
/**
* 配置数据库操作对象
* @return
*/
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
// 可以自动生成相关表
//repository.setCreateTableOnStartup(true);
repository.setDataSource(dataSource);
return repository;
}
配置类中配置自动登录
package per.xgt.config;
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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
/**
* @Author: gentao9527
* @date: 2022/8/7 15:52
* @Description: TODO
* @Version: 1.0
*/
@Configuration
public class SecurityMyConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private DataSource dataSource;
/**
* 配置数据库操作对象
* @return
*/
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
// 可以自动生成相关表
//repository.setCreateTableOnStartup(true);
repository.setDataSource(dataSource);
return repository;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
public PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
/**
* 配置自定义用户登录页面
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 退出登录
http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
// 配置没有权限访问跳转到自己自定义的页面
http.exceptionHandling().accessDeniedPage("/error/unAuth.html");
// 自定义自己编写的登录页面
http.formLogin()
// 登录页面的地址
.loginPage("/login.html")
// 登录访问路径
.loginProcessingUrl("/test/login")
// 登录成功后访问的路径
.defaultSuccessUrl("/test/index").permitAll()
.and().authorizeRequests()
// 设置不需要认证可以访问的路径
.antMatchers("/","/test/hello","/test/login").permitAll()
// 当前用户只有具有admin权限才可以访问这个路径
// .antMatchers("/test/index").hasAuthority("admin")
// .antMatchers("/test/index").hasAnyAuthority("admin","manager")
// 当前角色只有是admin角色才可以访问
// .antMatchers("/test/index").hasRole("admin")
.antMatchers("/test/index").hasAnyRole("admin,manager")
.anyRequest().authenticated()
.and().rememberMe().tokenRepository(persistentTokenRepository()).tokenValiditySeconds(600).userDetailsService(userDetailsService)
// 关闭CSRF防护
.and().csrf().disable();
}
}
登录页,name必须是 remember-me
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/test/login" method="post">
<input type="text" name="username">
<br>
<input type="password" name="password">
<br>
<input type="submit" value="登录">
<br>
<input type="checkbox" name="remember-me">
</form>
</body>
</html>
CSRF
配置类中启用csrf防护机制(SpringSecurity4.0开始默认开启)
@Override
protected void configure(HttpSecurity http) throws Exception {
// 退出登录
http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
// 配置没有权限访问跳转到自己自定义的页面
http.exceptionHandling().accessDeniedPage("/error/unAuth.html");
// 自定义自己编写的登录页面
http.formLogin()
// 登录页面的地址
.loginPage("/login.html")
// 登录访问路径
.loginProcessingUrl("/test/login")
// 登录成功后访问的路径
.defaultSuccessUrl("/test/index").permitAll()
.and().authorizeRequests()
// 设置不需要认证可以访问的路径
.antMatchers("/","/test/hello","/test/login").permitAll()
// 当前用户只有具有admin权限才可以访问这个路径
// .antMatchers("/test/index").hasAuthority("admin")
// .antMatchers("/test/index").hasAnyAuthority("admin","manager")
// 当前角色只有是admin角色才可以访问
// .antMatchers("/test/index").hasRole("admin")
.antMatchers("/test/index").hasAnyRole("admin,manager")
.anyRequest().authenticated()
.and().rememberMe().tokenRepository(persistentTokenRepository()).tokenValiditySeconds(600).userDetailsService(userDetailsService)
// 关闭CSRF防护
.and().csrf().disable();
}
在登录页添加一个隐藏域
<input
type="hidden"th:if="${_csrf}!=null"th:value="${_csrf.token}"name="_csrf />