公司动态

Spring Boot整合Spring Security

📅 2026/7/17 11:59:45
Spring Boot整合Spring Security
Spring Security是Spring官方推荐的认证、授权框架功能相比Apache Shiro功能更丰富也更强大但是使用起来更麻烦。如果使用过Apache Shiro学习Spring Security会比较简单一点两种框架有很多相似的地方。一、准备工作1、创建项目在IntelliJ IDEA中创建一个Spring Boot项目spring-boot-security2、添加依赖修改pom.xml添加项目所需的依赖。?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent version2.3.4.RELEASE/version groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId /parent version20260101/version groupIdcn.edu.sgu.www/groupId artifactIdspring-boot-security/artifactId descriptionSpring Boot整合Spring Security案例项目/description properties mysql.version8.0.28/mysql.version druid.version1.1.21/druid.version lombok.version1.18.22/lombok.version mybatis-plus.version3.5.15/mybatis-plus.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version${mysql.version}/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version${druid.version}/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version${lombok.version}/version /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version${mybatis-plus.version}/version /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-jsqlparser-4.9/artifactId version${mybatis-plus.version}/version /dependency /dependencies build plugins plugin version2.3.4.RELEASE/version groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build /project3、修改配置将配置文件application.properties重命名为application.yml修改配置文件的内容。server: port: 8080 servlet: context-path: / spring: datasource: username: root password: root url: jdbc:mysql://localhost:3306/spring-security driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource mybatis-plus: mapper-locations: classpath:mapper/*Mapper.xml logging: level: springfox: error cn.edu.sgu.www.security: debug system: login-page: /login.html login-url: /user/login index-page: /index.html logout-url: /user/logout parameter: username: username password: password white-url: - /js/** - /css/** - /images/** - /user/login - /login.html二、创建相关的类公共类ResponseCode.java响应状态码枚举类package cn.edu.sgu.www.security.restful; /** * 响应状态码 * author 沐雨橙风ιε * version 1.0 */ public enum ResponseCode { /** * 请求成功 */ OK(200), /** * 失败的请求 */ BAD_REQUEST(400), /** * 未授权 */ UNAUTHORIZED(401), /** * 禁止访问 */ FORBIDDEN(403), /** * 找不到该状态不可用 */ NOT_FOUND(404), /** * 不可访问 */ NOT_ACCEPTABLE(406), /** * 冲突 */ CONFLICT(409), /** * 服务器发生异常 */ ERROR(500); private final Integer value; ResponseCode(Integer value) { this.value value; } public Integer getValue() { return value; } }GlobalException.java自定义的全局异常类package cn.edu.sgu.www.security.exception; import cn.edu.sgu.www.security.restful.ResponseCode; import lombok.Data; import lombok.EqualsAndHashCode; /** * 自定义的全局异常类 * author 沐雨橙风ιε * version 1.0 */ Data EqualsAndHashCode(callSuper true) public class GlobalException extends RuntimeException { private ResponseCode responseCode; public GlobalException(ResponseCode responseCode, String message) { super(message); setResponseCode(responseCode); } }JsonResult.java自定义的统一响应实体类package cn.edu.sgu.www.security.restful; import lombok.Data; /** * 响应实体类 * author 沐雨橙风ιε * version 1.0 */ Data public class JsonResultT { /** * 响应数据 */ private T data; /** * 响应状态码 */ private Integer code; /** * 响应提示信息 */ private String message; public static JsonResultVoid success() { return success(null); } public static JsonResultVoid success(String message) { return success(message, null); } public static T JsonResultT success(String message, T data) { JsonResultT jsonResult new JsonResult(); jsonResult.setCode(ResponseCode.OK.getValue()); jsonResult.setMessage(message); jsonResult.setData(data); return jsonResult; } public static JsonResultVoid error(String message) { JsonResultVoid jsonResult new JsonResult(); jsonResult.setCode(ResponseCode.ERROR.getValue()); jsonResult.setMessage(message); return jsonResult; } public static JsonResultVoid error(ResponseCode responseCode, Throwable e) { return error(responseCode, e.getMessage() ! null ? e.getMessage() : 系统发生异常请联系管理员); } public static JsonResultVoid error(ResponseCode responseCode, String message) { JsonResultVoid jsonResult new JsonResult(); jsonResult.setCode(responseCode.getValue()); jsonResult.setMessage(message); return jsonResult; } }GlobalExceptionHandler.java全局异常处理类package cn.edu.sgu.www.security.restful.handler; import cn.edu.sgu.www.security.exception.GlobalException; import cn.edu.sgu.www.security.restful.JsonResult; import cn.edu.sgu.www.security.restful.ResponseCode; import org.springframework.http.HttpStatus; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.servlet.http.HttpServletResponse; import java.util.Objects; /** * 全局异常处理类 * author 沐雨橙风ιε * version 1.0 */ RestControllerAdvice public class GlobalExceptionHandler { /** * 处理GlobalException * param e GlobalException * return JsonResultVoid */ ExceptionHandler(GlobalException.class) public JsonResultVoid handleGlobalException(HttpServletResponse response, GlobalException e) { printMessage(e); response.setStatus(e.getResponseCode().getValue()); return JsonResult.error(e.getResponseCode(), e); } /** * 处理BindException * param e BindException * return JsonResultVoid */ ExceptionHandler(BindException.class) ResponseStatus(HttpStatus.BAD_REQUEST) public JsonResultVoid handleBindException(BindException e) { printMessage(e); BindingResult bindingResult e.getBindingResult(); FieldError fieldError bindingResult.getFieldError(); String defaultMessage Objects.requireNonNull(fieldError).getDefaultMessage(); return JsonResult.error(ResponseCode.BAD_REQUEST, defaultMessage); } /** * 处理Exception * param e Exception * return JsonResultVoid */ ExceptionHandler(Exception.class) ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public JsonResultVoid handleException(Exception e) { printMessage(e); return JsonResult.error(ResponseCode.ERROR, e); } /** * 打印异常信息 * param e Exception */ private void printMessage(Exception e) { System.out.println(e.getMessage()); e.printStackTrace(); } }业务类User.javapackage cn.edu.sgu.www.security.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.io.Serializable; import java.time.LocalDateTime; /** * author 沐雨橙风ιε * version 1.0 */ Data TableName(user) public class User implements Serializable { private static final long serialVersionUID 18L; TableId(value id, type IdType.INPUT) private String id; /** * 用户昵称 */ private String name; /** * 用户性别 */ private Integer gender; /** * 登录账号 */ private String username; /** * 登录密码 */ private String password; /** * 联系方式 */ private String phone; /** * 是否封禁 */ private boolean disabled; }UserMapper.javapackage cn.edu.sgu.www.security.mapper; import cn.edu.sgu.www.security.entity.User; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; /** * author 沐雨橙风ιε * version 1.0 */ Mapper public interface UserMapper extends BaseMapperUser { }UserLoginDTO.javapackage cn.edu.sgu.www.security.dto; import cn.edu.sgu.www.security.consts.ErrorMessages; import lombok.Data; import javax.validation.constraints.NotBlank; import java.io.Serializable; /** * author 沐雨橙风ιε * version 1.0 */ Data public class UserLoginDTO implements Serializable { private static final long serialVersionUID 18L; /** * 用户名 */ NotBlank(message 用户名 ErrorMessages.NOT_NULL) private String username; /** * 密码 */ NotBlank(message 密码 ErrorMessages.NOT_NULL) private String password; }UserService.javapackage cn.edu.sgu.www.security.service; import cn.edu.sgu.www.security.dto.UserLoginDTO; import cn.edu.sgu.www.security.entity.User; /** * author 沐雨橙风ιε * version 1.0 */ public interface UserService { /** * 登录认证 * param userLoginDTO 用户登录信息 */ void login(UserLoginDTO userLoginDTO); /** * 退出登录 */ void logout(); /** * 通过ID查询用户信息 * param userId 用户ID */ User selectById(String userId); }UserController.javapackage cn.edu.sgu.www.security.controller; import cn.edu.sgu.www.security.dto.UserLoginDTO; import cn.edu.sgu.www.security.entity.User; import cn.edu.sgu.www.security.restful.JsonResult; import cn.edu.sgu.www.security.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * author 沐雨橙风ιε * version 1.0 */ RestController RequiredArgsConstructor RequestMapping(path /user, produces application/json;charsetutf-8) public class UserController { private final UserService userService; RequestMapping(value /login, method RequestMethod.POST) public JsonResultVoid login(Validated UserLoginDTO userLoginDTO) { userService.login(userLoginDTO); return JsonResult.success(登录成功); } RequestMapping(value /logout, method RequestMethod.POST) public JsonResultVoid logout() { userService.logout(); return JsonResult.success(登出成功); } RequestMapping(value /selectById, method RequestMethod.GET) public JsonResultUser selectById(RequestParam(value id) String userId) { User user userService.selectById(userId); return JsonResult.success(null, user); } }UserServiceImpl.javapackage cn.edu.sgu.www.security.service.impl; import cn.edu.sgu.www.security.dto.UserLoginDTO; import cn.edu.sgu.www.security.entity.User; import cn.edu.sgu.www.security.mapper.UserMapper; import cn.edu.sgu.www.security.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; /** * author 沐雨橙风ιε * version 1.0 */ Service RequiredArgsConstructor public class UserServiceImpl implements UserService { private final UserMapper userMapper; private final AuthenticationManager authenticationManager; Override public void login(UserLoginDTO userLoginDTO) { Authentication authentication new UsernamePasswordAuthenticationToken( userLoginDTO.getUsername(), userLoginDTO.getPassword() ); authenticationManager.authenticate(authentication); } Override public void logout() { // todo } Override public User selectById(String userId) { return userMapper.selectById(userId); } }配置类SystemProperties.java创建配置读取类SystemPropertiespackage cn.edu.sgu.www.security.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.List; import java.util.Map; /** * author 沐雨橙风ιε * version 1.0 */ Data Component ConfigurationProperties(prefix system) public class SystemProperties { /** * 登录页面 */ private String loginPage; /** * 登录的请求地址 */ private String loginUrl; /** * 登录成功后跳转的页面 */ private String indexPage; /** * 退出登录的请求地址 */ private String logoutUrl; /** * 白名单 */ private ListString whiteUrl; /** * 登录的参数 */ private MapString, String parameter; }MybatisPlusConfig.javapackage cn.edu.sgu.www.security.config; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Mybatis-Plus配置类 * author 沐雨橙风ιε * version 1.0 */ Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 防全表更新与删除插件 interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); // 分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }Spring Security相关的类UserDetailsServiceImpl.javaUserDetailsService接口是Spring Security中非常重要的接口在登录认证的时候会通过这个接口的loadUserByUsername()方法获取用户的信息来完成登录的用户名、密码校验完成登录流程。package cn.edu.sgu.www.security.security; import cn.edu.sgu.www.security.consts.ErrorMessages; import cn.edu.sgu.www.security.entity.User; import cn.edu.sgu.www.security.exception.GlobalException; import cn.edu.sgu.www.security.mapper.UserMapper; import cn.edu.sgu.www.security.restful.ResponseCode; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.BadCredentialsException; 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.Component; import java.util.ArrayList; import java.util.List; /** * author 沐雨橙风ιε * version 1.0 */ Component RequiredArgsConstructor public class UserDetailsServiceImpl implements UserDetailsService { private final UserMapper userMapper; Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 查询用户信息 User user selectByUsername(username); if (user null) { throw new BadCredentialsException(登录失败用户名或密码错误); } else { String[] permissions selectPermissions(username); return org.springframework.security.core.userdetails.User.builder() .username(user.getUsername()) .password(user.getPassword()) .disabled(user.isDisabled()) .authorities(permissions) .accountLocked(false) .accountExpired(false) .credentialsExpired(false) .build(); } } /** * 通过用户名查询用户信息 * param username 用户名 * return User */ private User selectByUsername(String username) { QueryWrapperUser queryWrapper new QueryWrapper(); queryWrapper.eq(username, username); return userMapper.selectOne(queryWrapper); } /** * 通过用户名查询用户权限 * param username 用户名 */ private String[] selectPermissions(String username) { if (username null) { throw new GlobalException(ResponseCode.BAD_REQUEST, 用户名 ErrorMessages.NOT_NULL); } ListString permissions new ArrayList(); permissions.add(/user/login); permissions.add(/user/logout); permissions.add(/user/selectById); return permissions.toArray(new String[] { }); } }LoginSuccessHandler.java登录成功的处理器package cn.edu.sgu.www.security.security; import cn.edu.sgu.www.security.config.SystemProperties; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * Spring Security登录成功处理器 * author 沐雨橙风ιε * version 1.0 */ RequiredArgsConstructor public class LoginSuccessHandler implements AuthenticationSuccessHandler { private final SystemProperties systemProperties; Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { response.sendRedirect(systemProperties.getIndexPage()); } }LoginFailHandler.java登录失败的处理器package cn.edu.sgu.www.security.security; import cn.edu.sgu.www.security.exception.GlobalException; import cn.edu.sgu.www.security.restful.ResponseCode; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * author heyunlin * version 1.0 */ public class LoginFailHandler implements AuthenticationFailureHandler { Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) { throw new GlobalException(ResponseCode.BAD_REQUEST, 登录失败用户名或密码错误~); } }SecurityConfig.java在项目根包下创建security的配置类config.SecurityConfigpackage cn.edu.sgu.www.security.config; import cn.edu.sgu.www.security.security.LoginFailHandler; import cn.edu.sgu.www.security.security.LoginSuccessHandler; import lombok.RequiredArgsConstructor; 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; /** * Spring Security配置类 * author 沐雨橙风ιε * version 1.0 */ Configuration RequiredArgsConstructor public class SecurityConfig extends WebSecurityConfigurerAdapter { private final SystemProperties systemProperties; Bean public PasswordEncoder passwordEncoder() { return new PasswordEncoder() { Override public String encode(CharSequence charSequence) { return (String) charSequence; } Override public boolean matches(CharSequence charSequence, String s) { return charSequence.equals(s); } }; } Bean Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } Override protected void configure(HttpSecurity http) throws Exception { // 禁用防跨域攻击 http.csrf().disable(); // 配置各请求路径的认证与授权 http.formLogin() .loginPage(systemProperties.getLoginPage()) // 自定义登录页面的地址 .loginProcessingUrl(systemProperties.getLoginUrl()) // 处理登录的接口地址 .usernameParameter(systemProperties.getParameter().get(username)) // 用户名的参数名 .passwordParameter(systemProperties.getParameter().get(password)) // 密码的参数名 .successHandler(new LoginSuccessHandler(systemProperties)) //.successForwardUrl(/index.html) // 登录成功跳转的地址 .failureHandler(new LoginFailHandler()); // 登录失败的处理器 // 退出登录相关配置 http.logout() .logoutUrl(systemProperties.getLogoutUrl()) // 退出登录的接口地址 .logoutSuccessUrl(systemProperties.getLoginUrl()); // 退出登录成功跳转的地址 // 配置认证规则 String[] toArray systemProperties.getWhiteUrl().toArray(new String[]{}); http.authorizeRequests() .antMatchers(toArray).permitAll() // 白名单也就是不需要登录也能访问的资源 .anyRequest().authenticated(); } }项目的包结构文章就分享到这里了文章的代码已上传到Gitee可按需获取~Spring Boot整合Spring Security案例项目https://gitee.com/muyu-chengfeng/spring-boot-security.git