Spring Security
spring security目的:验证,授权,攻击防护。
spring security原理:创建大量的filter和interceptor来进行请求的验证和拦截,以此来达到安全的效果。
spring security本质:是一个过滤链,有很多过滤器
FilterSecurityInterceptor:访问授权,是一个方法级的权限过滤器,基本位于过滤器的最底部;
ExceptionTranslationFilter:异常过滤器,处理在认证过程中抛出的异常;
UsernamePasswordAuthenticationFilter:基于用户名和密码的身份验证过滤器,对/login的POST请求做拦截,校验表单中用户名、密码;
UserDetailsService:可以通过loadUserByUsername获取UserDetails对象。该接口供spring security进行用户验证。通常使用自定义一个CustomUserDetailsService来实现UserDetailsService接口,通过自定义查询UserDetails。【查询数据库用户名和密码过程】
AuthenticationManager:用来进行验证,如果验证失败会抛出相对应的异常*
PasswordEncoder:密码加密器
BCryptPasswordEncoder:哈希算法加密
NoOpPasswordEncoder:不使用加密
WebSecurityConfigurerAdapter:
AuthenticationManagerBuilder:
JwtAuthenticationFilter:登录认证检查过滤器
JwtLoginFilter:登录认证过滤器
LogoutFilter:登出过滤器
ResourceServerConfigurerAdapter:资源认证服务配置
AuthorizationServerSecurityConfigurer:授权认证服务配置
*
*
TokenLoginFilter
TokenAuthFilter
BasicAuthenticationFilter
*
*
1、pom.xml 文件中添加依赖
spring-boot-starter-web
spring-boot-starter-security
jjwt
fastjson
<?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.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demoSecurity</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demoSecurity</name>
<description>demoSecurity</description>
<properties>
<java.version>11</java.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>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、添加一个TestController【使用默认用户和随机生成的密码登录】
package com.example.demos.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("hello")
public String hello() {
return "hello";
}
}
启动项目,这样在启动的时候会在控制台显示随机生成的密码
spring security 默认的用户名是user,启动的时候会在控制台显示随机生成的密码
访问http://localhost:8080/test/hello
重定向到http://localhost:8080/login,这个页面是spring默认的
填入user 和 上图显示的81b60805-e799-4596-a404-17616dc56ecf,那么就会正常的访问/test/hello接口
*
3、配置文件,使用yml文件定义的用户名、密码登录【配置文件】
spring:
security:
user:
name: root
password: root
输入root/root登录,可以正常访问/test/hello接口
4、配置类,实现用户名、密码登录【配置类】【默认配置】
package com.example.demosecurity0218.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.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity //加不加好像都行
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("123456")).roles("admin")
.and()
.withUser("user").password(passwordEncoder().encode("123456")).roles("user");
}
/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin() // 表单登录
.and()
.authorizeRequests() // 认证配置
.anyRequest() // 所有请求
.authenticated() // 用户登录后可访问
.and()
.httpBasic(); // 都需要身份验证
}
}
访问http://localhost:8080/test/hello
重定向到http://localhost:8080/login,这个页面是spring默认的
填入用户名:admin,密码:123456,那么就会正常的访问/test/hello接口
5、配置类,实现用户名、密码登录【配置类】
新建 config 包,新建 SecurityCofig.java 文件
package com.example.demos.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.BCrypt;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.net.PasswordAuthentication;
@Configuration
@EnableWebSecurity
//@EnableWebSecurity(debug = true)
//@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityCofig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("123456")).roles("ADMIN")
.and()
.withUser("user").password(passwordEncoder().encode("123456")).roles("USER")
.and()
.withUser("king").password(passwordEncoder().encode("123456")).roles("USER", "ADMIN");;
}
/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//没有访问权限跳转到自定义页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
/**
* 方式1:默认配置
* 所有的请求访问都需要被授权。
* 使用 form 表单进行登陆(默认路径为/login)。
* 防止 CSRF 攻击、 XSS 攻击。
* 启用 HTTP Basic 认证
*/
http.
authorizeRequests().
anyRequest().authenticated().
and().
formLogin().
and().
httpBasic();
/**
* 方式2:高级配置
*/
http
// CSRF禁用,因为不使用session
.csrf().disable()
// 过滤请求
.authorizeRequests()
// 对于登录login 验证码captchaImage 允许匿名访问
.antMatchers("/login", "/captchaImage").anonymous()
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers("/profile/**").permitAll()
.antMatchers("/common/download**").permitAll()
.antMatchers("/common/download/resource**").permitAll()
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/*/api-docs").permitAll()
.antMatchers("/druid/**").permitAll()
.antMatchers("/flowable/**").permitAll()
.antMatchers("/socket/**").permitAll()
.antMatchers("/user/**").permitAll()
.antMatchers("/api/common/**").permitAll()
.antMatchers("/api/contract/**").permitAll()
.antMatchers("/api/project/**").permitAll()
.antMatchers("/api/document/**").permitAll()
.antMatchers("/api/purchase/**").permitAll()
.antMatchers("/login","/oauth/authorize").permitAll()//设置哪些路径可以直接访问,不需要认证.formLogin() //自定义编写登录页面
.antMatchers("/login","/oauth/authorize").hasAuthority("")
.antMatchers("/login","/oauth/authorize").hasAnyAuthority("")
.antMatchers("/login","/oauth/authorize").hasRole("")
.antMatchers("/login","/oauth/authorize").hasAnyRole("")
// 除上面外的所有请求全部需要鉴权认证
.anyRequest()
.authenticated()
.and()
//显示 HTTP ERROR 403
.headers().frameOptions().disable();
//使用 form 表单进行登陆(默认路径为/login)
//.formLogin().permitAll();
}
}
访问http://localhost:8080/test/hello
重定向到http://localhost:8080/login,这个页面是spring默认的
填入用户名:admin,密码:123456,那么就会正常的访问/test/hello接口
6、自定义实现类【UserDetailsService】
在 config 包,新建 MySecurityConfiguration.java 文件
package com.example.demos.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;
@Configuration
public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
新建 service 包,新建 MyUserDetailsService.java 文件
package com.example.demos.service;
import org.springframework.context.annotation.Configuration;
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;
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//可以查询数据库
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User("admin",new BCryptPasswordEncoder().encode("123123"),authorities);
}
}
输入admin/123123登录,可以正常访问/test/hello接口
7、hasAuthority、hasAnyAuthority、hasRole、hasAnyRole
.antMatchers("/login","/oauth/authorize").hasAuthority("")
.antMatchers("/login","/oauth/authorize").hasAnyAuthority("")
.antMatchers("/login","/oauth/authorize").hasRole("")
.antMatchers("/login","/oauth/authorize").hasAnyRole("")
8、配置文件
跨域配合文件:CorsConfig.java
接口生成文件:SwaggerConfig.java
安全配置文件:WebSecurityConfig.java
8.1、CorsConfig.java 跨域配置
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").
allowedOrigins("https://www.***.com"). //允许跨域的域名,可以用*表示允许任何域名使用
allowedMethods("GET", "POST", "DELETE", "PUT").
//allowedMethods("*"). //允许任何方法(post、get等)
allowCredentials(true). //带上cookie信息
allowedHeaders("*"). //允许任何请求头
maxAge(3600).
exposedHeaders(HttpHeaders.SET_COOKIE).maxAge(3600L); //maxAge(3600)表明在3600秒内,不需要再发送预检验请求,可以缓存该结果
}
}
8.2、SwaggerConfig.java
*
*
*
8.3、WebSecurityConfig.java
*
*
*
*
*
*
*