前言
随着Spring Boot和Spring Security的不断演进,以及JWT(JSON Web Tokens)和Redis等技术的广泛应用,我们迎来了一个全新的、更加强大和灵活的安全认证与授权时代。本文将详细搭建SpringBoot3+SpringSecurity6+JWT+Redis项目框架的全过程,从理论学习到项目实战,从遇到问题到解决问题的每一个细节。
所用到的技术
- springboot 3.2.5
- java jdk 17
- springsecurity6
- springJPA
- mybatis
- lombok
- redis 3.2
- fastjson
- knife4j
- mysql 5.7.24
导入依赖
<!--mysql依赖-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>3.0.3</version>
<scope>test</scope>
</dependency>
<!--引入druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>5.3.22</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-core</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-spring-web</artifactId>
<version>3.0.0</version>
</dependency>
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--fastjson依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<!--jpa依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--引入SpringSecurity-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
RBAC权限模型
RBAC(Role-Based Access Control,基于角色的访问控制)是一种广泛使用的权限控制模型,它通过将权限分配给角色,而不是直接授予用户,从而简化了权限管理。在RBAC模型中,用户通过成为某个角色的成员来获得相应的权限,而这些权限定义了用户能够执行的操作或对资源的访问级别。
用户表
CREATE TABLE `user` (
`id` bigint(11) NOT NULL COMMENT '主键',
`name` varchar(255) DEFAULT NULL COMMENT '姓名',
`username` varchar(255) DEFAULT NULL COMMENT '用户名',
`password` varchar(255) DEFAULT NULL COMMENT '密码',
`status` int(11) DEFAULT NULL COMMENT '0:禁用 1:启用',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`create_by` int(11) DEFAULT NULL COMMENT '创建人id',
`update_by` int(11) DEFAULT NULL COMMENT '修改人id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息';
角色表
CREATE TABLE `role` (
`id` bigint(20) NOT NULL COMMENT '主键',
`role` varchar(255) DEFAULT NULL COMMENT '角色名称',
`name` varchar(255) DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
权限表
CREATE TABLE `menu` (
`id` bigint(20) NOT NULL COMMENT '主键',
`permission` varchar(255) DEFAULT NULL COMMENT '权限',
`name` varchar(255) DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
用户--角色表
CREATE TABLE `user_role` (
`user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
`role_id` bigint(20) DEFAULT NULL COMMENT '角色id'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
角色--权限表
CREATE TABLE `role_menu` (
`role_id` bigint(20) DEFAULT NULL COMMENT '角色id',
`permission_id` bigint(20) DEFAULT NULL COMMENT '权限id'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
前期准备
后端统一响应结果
@Data
public class Result<T> implements Serializable {
private Integer code; //编码:1成功,0和其它数字为失败
private String msg; //错误信息
private T data; //数据
public static <T> Result<T> success() {
Result<T> result = new Result<T>();
result.code = 1;
return result;
}
public static <T> Result<T> success(T object) {
Result<T> result = new Result<T>();
result.data = object;
result.code = 1;
return result;
}
public static <T> Result<T> error(String msg) {
Result result = new Result();
result.msg = msg;
result.code = 0;
return result;
}
}
jwt工具类
public class JwtUtil {
/**
* 生成jwt
* 使用Hs256算法, 私匙使用固定秘钥
* @param secretKey jwt秘钥
* @param ttlMillis jwt过期时间(毫秒)
* @param claims 设置的信息
* @return
*/
public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
// 指定签名的时候使用的签名算法,也就是header那部分
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成JWT的时间
long expMillis = System.currentTimeMillis() + ttlMillis;
Date exp = new Date(expMillis);
// 设置jwt的body
JwtBuilder builder = Jwts.builder()
// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
// 设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
// 设置过期时间
.setExpiration(exp);
return builder.compact();
}
/**
* Token解密
*
* @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
* @param token 加密后的token
* @return
*/
public static Claims parseJWT(String secretKey, String token) {
// 得到DefaultJwtParser
Claims claims = Jwts.parser()
// 设置签名的秘钥
.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
// 设置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
}
常量
/**
* JWT中存放的Claims常量
* @author gs
*/
public class JwtClaimsConstant {
public static final String USER_ID = "userId";
public static final String USERNAME = "username";
public static final String NAME = "name";
public static final String TOKEN = "token";
}
/**
* 消息提示类常量
* @author gs
*/
public class LoginConstant {
public static final String USER_NOT_FOUND = "用户不存在";
public static final String USER_NOT_ENABLE = "用户未启用";
public static final String USER_NOT_LOGIN = "用户未登录";
public static final String USERNAME_IS_NULL = "用户名为空";
public static final String UNKNOWN_ERROR = "未知错误";
public static final String USER_LOGIN_SUCCESS = "用户登录成功";
public static final String LOGIN_FAILED = "登录失败";
public static final String PASSWORD_ERROR = "密码错误";
public static final String USERNAME_OR_PASSWORD_ERROR = "用户名或密码错误";
public static final String ANONYMOUS_USER_NO_PERMISSION = "匿名用户无权限访问";
public static final String LOGOUT_SUCCESS = "退出成功";
}
/**
* 权限问题
* @author gs
*/
public class PermissionConstant {
public static final String PermissionDenied = "权限不足";
}
/**
* redis常量
*/
public class RedisConstant {
// 存储在redis中的token前缀
public static final String TOKEN_HEADER = "gs_";
}
/**
* token异常处理常量
* @author gs
*/
public class TokenConstant {
// token为空
public static final String TOKEN_IS_NULL = "token为空";
// token无效
public static final String TOKEN_INVALID = "token无效";
// token过期
public static final String TOKEN_EXPIRED = "token过期";
}
实体类
dto
@Data
@ApiModel(description = "用户登录信息")
public class UserLoginDTO implements Serializable {
@ApiModelProperty(value = "用户名", required = true)
private String username;
@ApiModelProperty(value = "密码", required = true)
private String password;
}
实体
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "user")
@Entity
public class User {
// 用户唯一标识
@Id
private Long id;
// 姓名
private String name;
// 用户名
private String username;
// 密码
private String password;
// 状态: 0:禁用 1:启用
private Integer status;
// 创建时间
private LocalDateTime createTime;
// 更新时间
private LocalDateTime updateTime;
// 创建人
private Integer createBy;
// 更新人
private Integer updateBy;
}
vo
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(description = "用户登录返回信息")
public class UserLoginVO implements Serializable {
@ApiModelProperty("主键值")
private Long id;
@ApiModelProperty("jwt令牌")
private String token;
@ApiModelProperty("姓名")
private String name;
@ApiModelProperty("用户名")
private String username;
}
config配置类
knife4j配置类
@Configuration
public class Knife4jConfig {//对于配置类要求可以看懂即可,不用反复去写,将来可以CV
@Bean
public OpenAPI springShopOpenApi() {
return new OpenAPI()
// 接口文档标题
.info(new Info().title("标题")
// 接口文档简介
.description("项目简介")
// 接口文档版本
.version("1.0.0")
// 开发者联系方式
.contact(new Contact().name("顾随")
.email("****")));
}
}
redis配置
@Configuration
public class RedisConfig {
/**
* 自定义redisTemplate
* @return
*/
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
//设置redis连接工厂对象
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置redis key的序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
Mapper
MenuMapper
@Mapper
public interface MenuMapper {
/**
* 根据用户id获取权限
* @param userId
* @return
*/
@Select("SELECT\n" +
"m.permission \n" +
"FROM\n" +
"user_role ur\n" +
"INNER JOIN role r ON ur.role_id = r.id\n" +
"INNER JOIN role_menu rm ON rm.role_id = r.id\n" +
"INNER JOIN menu m ON m.id = rm.permission_id \n" +
"WHERE\n" +
"user_id = #{userId}")
List<String> selectPermissionByUserId(Long userId);
}
UserMapper
@Mapper
public interface UserMapper {
/**
* 根据用户名查询用户
* @param username
* @return
*/
@Select("select * from user where username = #{username} and status = 1")
User findByUsername(String username);
/**
* 根据id查询用户
* @param id
* @return
*/
@Select("select * from user where id = #{id} and status = 1")
User findById(Long id);
}
repository
public interface UserRepository extends JpaRepository<User, Long> {
/**
* 根据用户名查询用户
* @param username 用户名
* @return 用户
*/
User findByUsername(String username);
}
异常处理
匿名用户无权限访问资源处理类
@Component
public class AnonymousAuthenticationHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
//设置客户端的响应的内容类型
response.setContentType("application/json;charset=UTF-8");
String result = null;
ServletOutputStream outputStream = response.getOutputStream();
// 消除循环引用
if (authException instanceof BadCredentialsException) {
result = JSON.toJSONString(Result.error(LoginConstant.USERNAME_OR_PASSWORD_ERROR), SerializerFeature.DisableCircularReferenceDetect);
} else if (authException instanceof InternalAuthenticationServiceException) {
result = JSON.toJSONString(Result.error(LoginConstant.USERNAME_IS_NULL), SerializerFeature.DisableCircularReferenceDetect);
} else {
result = JSON.toJSONString(Result.error(LoginConstant.ANONYMOUS_USER_NO_PERMISSION), SerializerFeature.DisableCircularReferenceDetect);
}
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
认证用户无权限访问的处理器
@Component
public class CustomerAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
// 发生这个异常,做出处理
response.setContentType("application/json;charset=utf-8");
// 获取输出流
ServletOutputStream outputStream = response.getOutputStream();
// 消除循环引用
String result = JSON.toJSONString(Result.error(PermissionConstant.PermissionDenied), SerializerFeature.DisableCircularReferenceDetect);
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
用户认证校验失败的处理器
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
//设置客户端响应编码格式
response.setContentType("application/json;charset=UTF-8");
//获取输出流
ServletOutputStream outputStream= response.getOutputStream();
String message = null;//提示信息
//判断异常类型
if(exception instanceof AccountExpiredException){
message = "账户过期,登录失败!";
}else if(exception instanceof BadCredentialsException){
message = "用户名或密码错误,登录失败!";
}else if(exception instanceof CredentialsExpiredException){
message = "密码过期,登录失败!";
}else if(exception instanceof DisabledException){
message = "账户被禁用,登录失败!";
}else if(exception instanceof LockedException){
message = "账户被锁,登录失败!";
}else if(exception instanceof InternalAuthenticationServiceException){
message = "账户不存在,登录失败!";
}else if(exception instanceof CustomerAuthenticationException){
message = exception.getMessage();
}else{
message = "登录失败!";
}
//将错误信息转换成JSON
String result = JSON.toJSONString(Result.error(message));
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
自定义验证异常
public class CustomerAuthenticationException extends AuthenticationException {
public CustomerAuthenticationException(String msg) {
super(msg);
}
}
jwt令牌过滤器
@Component
@RequiredArgsConstructor
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
// 登录失败的处理器
private final LoginFailureHandler loginFailureHandler;
// jwt变量
private final JwtProperties jwtProperties;
// 用户的mapper
private final UserMapper userMapper;
// 权限
private final MenuMapper menuMapper;
// redis配置
private final RedisTemplate redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
String uri = request.getRequestURI();
// 如果是登录接口,直接放行
if (!uri.startsWith("/user/login")) {
this.validateToken(request, response);
}
} catch (AuthenticationException e) {
loginFailureHandler.onAuthenticationFailure(request, response, e);
}
//放行
filterChain.doFilter(request, response);
}
/**
* token的校验
*
* @param request
* @param response
*/
public void validateToken(HttpServletRequest request, HttpServletResponse response) {
String token = request.getHeader(jwtProperties.getUserTokenName());
//如果请求头部没有获取到token,则从请求的参数中进行获取
if (ObjectUtils.isEmpty(token)) {
token = request.getParameter(jwtProperties.getUserTokenName());
}
if (ObjectUtils.isEmpty(token)) {
// 不通过,响应401状态码
response.setStatus(401);
throw new CustomerAuthenticationException(TokenConstant.TOKEN_IS_NULL);
}
// redis进校验
String redisToken = redisTemplate.opsForValue().get(RedisConstant.TOKEN_HEADER + token).toString();
if (ObjectUtils.isEmpty(redisToken)) {
response.setStatus(401);
throw new CustomerAuthenticationException(TokenConstant.TOKEN_EXPIRED);
}
LoginUser loginUser = null;
// 校验token
try {
Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
User user = userMapper.findById(userId);
// 授权操作
List<String> permission = menuMapper.selectPermissionByUserId(user.getId());
loginUser = new LoginUser(user, permission);
} catch (Exception e) {
// 不通过,响应401状态码
response.setStatus(401);
throw new CustomerAuthenticationException(TokenConstant.TOKEN_INVALID);
}
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
controller
@RestController
@RequestMapping("/user")
@Slf4j
@Tag(name = "用户登录接口")
@RequiredArgsConstructor
public class LoginController {
private final LoginService loginService;
// redis
private final RedisTemplate redisTemplate;
// jwt配置
private final JwtProperties jwtProperties;
@Operation(summary = "用户登录")
@PostMapping("/login")
public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDto)
{
UserLoginVO userLoginVO = loginService.login(userLoginDto);
return Result.success(userLoginVO);
}
@GetMapping("/hello")
@PreAuthorize("hasAuthority('user:normal')")
public Result<String> hello() {
return Result.success("hello user!");
}
@GetMapping("/admin")
@PreAuthorize("hasAuthority('user:manage')")
public Result<String> admin() {
return Result.success("hello admin!");
}
@PostMapping("/logout")
@Operation(summary = "用户退出")
public Result logout(HttpServletRequest request, HttpServletResponse response) {
// 在logout中获取token
String token = request.getHeader(jwtProperties.getUserTokenName());
if (ObjectUtils.isEmpty(token)) {
token = request.getParameter(jwtProperties.getUserTokenName());
}
if (ObjectUtils.isEmpty(token)) {
throw new CustomerAuthenticationException(TokenConstant.TOKEN_IS_NULL);
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication!=null) {
// 清空用户信息
new SecurityContextLogoutHandler().logout(request, response, authentication);
// 清空redis里面的token
redisTemplate.delete(RedisConstant.TOKEN_HEADER +token);
}
return Result.success(LoginConstant.LOGOUT_SUCCESS);
}
}
domain
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;
// 权限
private List<String> permissions;
public LoginUser(User user, List<String> permissions) {
this.user = user;
this.permissions = permissions;
}
public LoginUser(User user) {
this.user = user;
}
// 自定义权限列表集合
@JSONField(serialize = false)
private List<SimpleGrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (authorities != null) {
return authorities;
}
authorities = new ArrayList<>();
permissions.forEach(permission -> {
authorities.add(new SimpleGrantedAuthority(permission));
});
return authorities;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
service
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
private final MenuMapper menuMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 用户名为空
if (username.equals("")) {
throw new InternalAuthenticationServiceException(LoginConstant.USERNAME_IS_NULL);
}
// 根据用户名查询用户信息
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(LoginConstant.USER_NOT_FOUND);
}
// 授权操作
List<String> permission = menuMapper.selectPermissionByUserId(user.getId());
return new LoginUser(user,permission);
}
}
@Service
@RequiredArgsConstructor
public class LoginServiceImpl implements LoginService {
// redis配置
private final RedisTemplate redisTemplate;
private final JwtProperties jwtProperties;
@Autowired
private AuthenticationManager authenticationManager;
/**
* 用户登录
*
* @param userLoginDto
* @return
*/
@Override
public UserLoginVO login(UserLoginDTO userLoginDto) {
// 封装Authentication对象
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userLoginDto.getUsername(), userLoginDto.getPassword());
// 进行校验
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if (Objects.isNull(authenticate)) {
throw new LoginFailedException(LoginConstant.LOGIN_FAILED);
}
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
User user = loginUser.getUser();
// 登录成功后生成token
Map<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.USER_ID, user.getId());
String token = JwtUtil.createJWT(
jwtProperties.getUserSecretKey(),
jwtProperties.getUserTtl(),
claims
);
// 将用户token设置到Redis中
// token作为用户身份验证的凭证,需要在服务器端进行存储和管理
redisTemplate.opsForValue().set(
// 键名
RedisConstant.TOKEN_HEADER + token,
// 键值,此处键值相同,因为只需要存储token本身
token,
// 过期时间,使用JWT配置中的用户token过期时间
jwtProperties.getUserTtl(),
// 时间单位,指定过期时间为毫秒单位
TimeUnit.MILLISECONDS
);
UserLoginVO userLoginVO = UserLoginVO.builder()
.id(user.getId())
.name(user.getName())
.token(token)
.username(user.getUsername())
.build();
return userLoginVO;
}
}
核心配置
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
// 用户登录服务
private final UserDetailsServiceImpl userDetailsService;
// 用户认证校验失败处理器
private final LoginFailureHandler loginFailureHandler;
// 匿名用户无权限访问资源处理类
private final AnonymousAuthenticationHandler anonymousAuthenticationHandler;
// 认证用户无权限访问的处理器
private final CustomerAccessDeniedHandler customerAccessDeniedHandler;
// jwt过滤器
private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
/**
* 创建BCryptPasswordEncoder注入容器
* 此容器会自动讲密码进行加密,同时会生成随机盐,会产生不同的密文
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 创建AuthenticationManager注入容器
* 登录时需要调用AuthenticationManager.authenticate执行一次校验
* @param config
* @return
* @throws Exception
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 配置关闭csrf
.csrf(csrf -> csrf.disable())
// 支持跨域
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
// 配置请求拦截方式
.authorizeHttpRequests(auth -> auth
.requestMatchers("/user/login")
.permitAll()
.anyRequest()
.authenticated()
)
// 把token校验过滤器添加到过滤器链中
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
// 自定义异常处理
.exceptionHandling(exception -> exception
// 认证用户无权限访问的处理器
.accessDeniedHandler(customerAccessDeniedHandler)
// 匿名用户访问无权限的处理器
.authenticationEntryPoint(anonymousAuthenticationHandler))
// 用户认证校验失败处理器
.formLogin(configurer -> configurer.failureHandler(loginFailureHandler))
//不需要session
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
;
return http.build();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
// 提供自定义loadUserByUsername
authProvider.setUserDetailsService(userDetailsService);
// 指定密码编辑器
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
/**
* 配置跨域
* @return
*/
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*")); // 允许的来源
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); // 允许的方法
configuration.setAllowedHeaders(Arrays.asList("*")); // 允许的头部
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration); // 应用于所有路径
return source;
}
}
@EnableJpaAuditing // springJPA注解
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true) // springsecurity注解
public class Springboot3Application {
public static void main(String[] args) {
org.springframework.boot.SpringApplication.run(Springboot3Application.class, args);
}
}