Springboot集成SpringSecurity的介绍及使用
文章目录
- SpringSecurity简介
- SpringSecurity原理
- 1. 认证流程
- 2. 关键组件
- 3. 配置示例
- 项目示例
- pom.xml文件
- application.yml
- 添加springsecurity配置类
- 请求过滤
- 实现UserDetailsService
- 总结
SpringSecurity简介
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
SpringSecurity原理
在Spring Security中,认证(Authentication)是安全框架的核心功能之一,其目的是验证用户身份。Spring Security提供了强大的机制来处理用户认证,主要包括以下几个方面:
1. 认证流程
Spring Security的认证流程大致如下:
- 请求到达:用户通过HTTP请求访问受保护的资源。
- 认证过滤器:Spring Security的过滤器链开始工作,首先到达UsernamePasswordAuthenticationFilter(或其他类型的认证过滤器,如BasicAuthenticationFilter等)。
- 提取凭证:过滤器从请求中提取认证信息(如用户名和密码)。
- 用户详情服务:使用提取的凭证,通过UserDetailsService接口的实现来加载用户详细信息(如用户名、密码、权限等)。
- 验证:通过AuthenticationProvider验证用户凭证的正确性。
- 创建认证对象:如果凭证验证成功,创建一个Authentication对象,其中包含用户信息和权限。
- 安全上下文持有器:将Authentication对象存储在SecurityContextHolder中,以便后续的请求都能访问到当前认证的用户信息。
- 授权:根据用户的权限,决定是否允许访问资源。
- 响应请求:如果用户被授权访问,则返回请求的资源;否则返回未授权响应。
2. 关键组件
UserDetailsService:这是一个接口,用于加载用户特定数据。通常与数据库交互来获取用户信息。
@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate UserRepository userRepository;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userRepository.findByUsername(username);if (user == null) {throw new UsernameNotFoundException("User not found");}return new User(user.getUsername(), user.getPassword(), getAuthorities(user)); // 实现细节可能有所不同}
}
AuthenticationProvider:这是一个接口,用于验证用户的凭证。Spring Security提供了多种实现,如DaoAuthenticationProvider,它结合了UserDetailsService和密码编码器(如BCrypt)。
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
PasswordEncoder:用于对用户密码进行编码,增强安全性。Spring Security提供了多种编码器实现,如BCrypt。
@Bean
public PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();
}
3. 配置示例
在Spring Security配置中,你可以使用Java配置或XML配置来设置这些组件。以下是使用Java配置的示例:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate CustomUserDetailsService customUserDetailsService;@Autowiredprivate PasswordEncoder passwordEncoder;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder);}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/public/**").permitAll() // 公开的URL路径.anyRequest().authenticated() // 其他所有请求都需要认证.and().formLogin() // 启用表单登录功能.and().httpBasic(); // 启用HTTP Basic认证方式}
}
项目示例
pom.xml文件
<?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.3.12.RELEASE</version><relativePath /></parent><groupId>com.cy</groupId><artifactId>springboot-springsecurity</artifactId><version>0.0.1-SNAPSHOT</version><name>springboot-springsecurity</name><description>Spring Boot整合Spring Security案例项目</description><properties><java.version>1.8</java.version><mysql.version>8.0.28</mysql.version><druid.version>1.1.21</druid.version><lombok.version>1.18.22</lombok.version><mybatis.version>2.2.2</mybatis.version><mybatis-plus.version>3.4.0</mybatis-plus.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-test</artifactId><scope>test</scope></dependency><!--validation--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><!--spring security--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.0</version></dependency><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version></dependency><!--druid--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>${druid.version}</version></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version></dependency><!--mybatis--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>${mybatis.version}</version></dependency><!--mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</version></dependency><!--swagger2依赖--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
application.yml
server:port: 8083servlet:context-path: /spring:datasource:username: db_userpassword: db_passwordurl: jdbc:mysql://localhost:3306/spring-securitydriver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSourcemybatis-plus:mapper-locations: classpath:mapper/*.xmlconfiguration:map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImpltype-aliases-package: com.cy.entitylogging:level:springfox: errorcn.edu.sgu.www.security: debug
项目开发过程因为配置错误登录页面、登录路径,导致项目直接进入loadUserByUsername方法,不进入自己编写的登录接口,在配置时需要注意
添加springsecurity配置类
package com.cy.config;import com.cy.security.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;/*** Spring Security配置类* @version 1.0*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Beanpublic PasswordEncoder passwordEncoder() {return new PasswordEncoder() {@Overridepublic String encode(CharSequence charSequence) {return (String) charSequence;}@Overridepublic boolean matches(CharSequence charSequence, String s) {return charSequence.equals(s);}};}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity http) throws Exception {// 禁用防跨域攻击http.csrf().disable();http.sessionManagement().maximumSessions(1).expiredUrl( "/user/login");http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);}@Bean@Overrideprotected AuthenticationManager authenticationManager() throws Exception {return super.authenticationManager();}
}
请求过滤
JwtAuthenticationTokenFilter.java
package com.rc.security;import com.rc.entity.SysUser;
import com.rc.service.SysUserService;
import com.rc.utils.JwtUtil;import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import java.io.IOException;@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate SysUserService sysUserService;@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {String token = request.getHeader("Authorization");if (!StringUtils.hasText(token)) {filterChain.doFilter(request, response);return;}String userid;try {Claims claims = JwtUtil.parseJWT(token);userid = claims.getSubject();} catch (Exception e) {throw new RuntimeException(e);}SysUser user = sysUserService.getById(userid);if (user == null) {throw new RuntimeException("用户名未登录");}UserDetails loginUser = userDetailsService.loadUserByUsername(user.getUsername());UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(loginUser, null, null);// 如果是有效的jwt,那么设置该用户为认证后的用户SecurityContextHolder.getContext().setAuthentication(authenticationToken);filterChain.doFilter(request, response);}
}
实现UserDetailsService
package com.cy.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cy.dto.UserLoginDTO;
import com.cy.entity.User;
import com.cy.mapper.UserMapper;
import com.cy.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService, UserDetailsService {@Autowiredprivate AuthenticationManager authenticationManager;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 查询用户信息User user = selectByUsername(username);if (user == null) {throw new BadCredentialsException("登录失败,用户名不存在!");} else {List<String> permissions = selectPermissions(username);return org.springframework.security.core.userdetails.User.builder().username(user.getUsername()).password(user.getPassword()).accountExpired(false).accountLocked(false).disabled(!user.isEnable()).credentialsExpired(false).authorities(permissions.toArray(new String[] {})).build();}}/*** 通过用户名查询用户信息* @param username 用户名* @return User*/private User selectByUsername(String username) {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("username", username);List<User> list = list(queryWrapper);if (list.size() == 1) {return list.get(0);}return null;}/*** 通过用户名查询用户权限* @param username 用户名* @return List<String>*/private List<String> selectPermissions(String username) {if (username == null) {throw new RuntimeException("用户名不能为空");}List<String> permissions = new ArrayList<>();permissions.add("/user/login");permissions.add("/user/logout");return permissions;}@Overridepublic void login(UserLoginDTO userLoginDTO) {Authentication authentication = new UsernamePasswordAuthenticationToken(userLoginDTO.getUsername(),userLoginDTO.getPassword());authenticationManager.authenticate(authentication);}@Overridepublic void logout() {// todo}@Overridepublic User selectById(String userId) {return this.getById(userId);}}
项目地址:https://gitee.com/wangcheng626/spring-security-demo
总结
Spring Security通过上述流程和组件,提供了一套灵活且强大的用户认证机制。开发者可以根据需要自定义用户加载逻辑、密码编码策略等,从而满足不同的安全需求。通过合理配置,可以有效地保护Web应用免受未授权访问的威胁。