springboot 整合 Spring Security 上篇
1.创建springBoot 项目工程(spring6.0的底层、JDK17)
1.添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
配置完成启动访问controller会出现登录页面
默认用户user 密码 控制台uuid输出,跟踪源码可以看到
2 使用配置的用户名密码;
security:
user:
name: admin
password: 123456
使用自己配置的登录然后就可以访问控制层api
3.退出登录
http://127.0.0.1:8080/logout
2.基于内存的多用户管理
1.创建用户详情接口配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.provisioning.InMemoryUserDetailsManager;
//自定义配置类实现用户详情
@Configuration
public class MyUserSecurityConfig {
@Bean
public UserDetailsService userDetailsService() {
UserDetails user1 = User.builder().username("admin").password("123456").build();
UserDetails user2 = User.builder().username("test").password("123456").build();
InMemoryUserDetailsManager memoryUserDetailsManager = new InMemoryUserDetailsManager();
memoryUserDetailsManager.createUser(user1);
memoryUserDetailsManager.createUser(user2);
return memoryUserDetailsManager;
}
}
访问接口登录 test test 报错:如下
必须加密
修改配置类
package com.example.db.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
//自定义类实现用户详情接口
@Configuration
public class MyUserSecurityConfig {
@Bean
public UserDetailsService userDetailsService() {
UserDetails user1 = User.builder().username("admin").password("123456")
.roles("admin").build();
UserDetails user2 = User.builder().username("test").password("123456")
.roles("stu").build();
InMemoryUserDetailsManager memoryUserDetailsManager = new InMemoryUserDetailsManager();
memoryUserDetailsManager.createUser(user1);
memoryUserDetailsManager.createUser(user2);
return memoryUserDetailsManager;
}
//配置密码加密器
//没有加密NoOpPasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
通过上面配置可以正常登录,访问api接口,配置文件中的用户失效
3.密码加密
1.CSDN密码泄露事件,数据库明文存储
1.前端密码加密给后端,使用相同的密码加密规则,前端加密对比数据库密码校验规则。
常见加密算法MD5 sha RSA
2、使用PasswordEncoder接口提供的实现类
加盐干扰因子
BCryptPasswordEncoder 加密和校验原始密码
BCryptPasswordEncoder cryptPasswordEncoder=new BCryptPasswordEncoder();
String encode = cryptPasswordEncoder.encode("123456");
String encode2 = cryptPasswordEncoder.encode("123456");
String encode3 = cryptPasswordEncoder.encode("123456");
log.info(encode);
log.info(encode2);
log.info(encode3);
boolean pwd1 = cryptPasswordEncoder.matches("123456", encode);
boolean pwd2 = cryptPasswordEncoder.matches("123456", encode2);
boolean pwd3 = cryptPasswordEncoder.matches("123456", encode3);
log.info("pwd1="+pwd1);
log.info("pwd2="+pwd2);
log.info("pwd3="+pwd3);
加密上面文明密码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
//自定义类实现用户详情接口
@Configuration
public class MyUserSecurityConfig {
@Bean
public UserDetailsService userDetailsService() {
UserDetails user1 = User.builder().username("admin").password(passwordEncoder().encode("123456"))
.roles("admin").build();
UserDetails user2 = User.builder().username("test").password(passwordEncoder().encode("654321"))
.roles("stu").build();
InMemoryUserDetailsManager memoryUserDetailsManager = new InMemoryUserDetailsManager();
memoryUserDetailsManager.createUser(user1);
memoryUserDetailsManager.createUser(user2);
return memoryUserDetailsManager;
}
//配置密码加密器
//没有加密NoOpPasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
// return NoOpPasswordEncoder.getInstance();
return new BCryptPasswordEncoder();
}
}
在这里插入代码片
返回登录信息测试
添加控制器
package com.example.db.control;
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/loginUser")
public Authentication loginUser(Authentication authentication) {
return authentication;
}
@GetMapping("/loginUser2")
public Principal loginUser2(Principal principal) {
return principal;
}
@GetMapping("/loginUser3")
public Principal loginUser3() {
//通过安全上下文持有器
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication;
}
}
访问接口返回登录用户的信息如下
配置权限
UserDetails user1 = User.builder().username("admin").password(passwordEncoder().encode("123456"))
.roles("admin")
.authorities("stu:del") //配置权限
{
"authorities": [{
"authority": "stu:del"
}],
"details": {
"remoteAddress": "127.0.0.1",
"sessionId": "026ED34E46FC9022C29B200090F14924"
},
"authenticated": true,
"principal": {
"password": null,
"username": "admin",
"authorities": [{
"authority": "stu:del"
}],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true
},
"credentials": null,
"name": "admin"
}
4.基于方法的授权
开启@EnableGlobalMethodSecurity(prePostEnabled = true)
package com.example.db.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
//从 Spring Security 5.7.0-M2开始 WebSecurityConfigurerAdapter 被标记为过期,鼓励用户转向基于组件的 security 配置
@Configuration
@Slf4j
//全局方法授权
@EnableWebSecurity // 启用SpringSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry
.anyRequest().authenticated());
http.formLogin().permitAll();
return http.build();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/test/**");
}
}
控制器配置
package com.example.db.control;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/student")
@RestController
public class StudentController {
@GetMapping("/stu")
@PreAuthorize("hasAuthority('stu')")
public String test() {
return "I am a student";
}
}
5.处理结果返回JSON 前端好处理
响应报文VO
package com.example.db.pojo;
import lombok.Data;
/**
* 自定义响应结构
*/
@Data
public class Result {
// 响应业务状态
private Integer code;
// 响应消息
private String message;
// 响应中的数据
private Object data;
public Result() {
}
public Result(Object data) {
this.code = 200;
this.message = "OK";
this.data = data;
}
public Result(String message, Object data) {
this.code = 200;
this.message = message;
this.data = data;
}
public Result(Integer code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
public static Result ok() {
return new Result(null);
}
public static Result ok(String message) {
return new Result(message, null);
}
public static Result ok(Object data) {
return new Result(data);
}
public static Result ok(String message, Object data) {
return new Result(message, data);
}
public static Result build(Integer code, String message) {
return new Result(code, message, null);
}
public static Result build(Integer code, String message, Object data) {
return new Result(code, message, data);
}
}
1.认证成功处理
package com.example.db.config;
import com.example.db.pojo.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
//认证成功处理器
@Component
@Slf4j
public class AuthorSuccesssHandler implements AuthenticationSuccessHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Result result=Result.build(1,"登录成功");
String responsejson=objectMapper.writeValueAsString(result);
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(responsejson);
response.getWriter().flush();
}
}
2.认证失败处理器
package com.example.db.config;
import com.example.db.pojo.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
//认证失败处理器
@Component
public class AuthorFailHandler implements AuthenticationFailureHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
Result result=Result.build(-1,"登陆失败");
String responsejson=objectMapper.writeValueAsString(result);
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(responsejson);
response.getWriter().flush();
}
}
3.退出登录处理器
package com.example.db.config;
import com.example.db.pojo.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
//注销处理器
@Component
@Slf4j
public class AppLoginOutHandler implements LogoutSuccessHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Result result = Result.build(1, "退出登录成功");
String responsejson = objectMapper.writeValueAsString(result);
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(responsejson);
response.getWriter().flush();
}
}
4.资源访问拒绝处理器
package com.example.db.config;
import com.example.db.pojo.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
//资源访问拒绝处理器
@Slf4j
@Component
public class AppAcessDeiedHandler implements AccessDeniedHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
Result result = Result.build(0, "你没有权限访问资源");
String responsejson = objectMapper.writeValueAsString(result);
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(responsejson);
response.getWriter().flush();
}
}
5.配置各种处理器
package com.example.db.config;
import lombok.extern.slf4j.Slf4j;
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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.web.SecurityFilterChain;
//从 Spring Security 5.7.0-M2开始 WebSecurityConfigurerAdapter 被标记为过期,鼓励用户转向基于组件的 security 配置
@Configuration
@Slf4j
//全局方法授权
@EnableWebSecurity // 启用SpringSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
//认证成功处理器
@Autowired
private AuthorSuccesssHandler authorSuccesssHandler;
@Autowired
//认证失败处理器
private AuthorFailHandler authorFailHandler;
//退出登录处理器
@Autowired
private AppLoginOutHandler appLoginOutHandler;
//访问拒绝处理器
@Autowired
private AppAcessDeiedHandler appAcessDeiedHandler;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry
.anyRequest().authenticated());
http.formLogin()
.successHandler(authorSuccesssHandler)//认证成功
.failureHandler(authorFailHandler)//认证失败
.permitAll();
http.logout().logoutSuccessHandler(appLoginOutHandler); //退出登录
http.exceptionHandling().accessDeniedHandler(appAcessDeiedHandler);//访问资源失败
return http.build();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/test/**");
}
}