Springboot+Spring Security实现前后端分离登录认证及权限控制的示例代码
2022-04-15 16:36:59 来源:易采站长站 作者:
目录
前言本文主要的功能 一、准备工作1、统一错误码枚举2、统一json返回体3、返回体构造工具4、pom5、配置文件二、数据库表设计初始化表数据语句三、Spring Security核心配置:WebSecurityConfig四、用户登录认证逻辑:UserDetailsService1、创建自定义UserDetailsService2、准备service和dao层方法五、用户密码加密六、屏蔽Spring Security默认重定向登录页面以实现前后端分离功能1、实现登录成功/失败、登出处理逻辑2、在WebSecurityConfig中的configure(HttpSecurity http)方法中声明八、会话管理(登录过时、限制单用户或多用户登录等)1、限制登录用户数量2、处理账号被挤下线处理逻辑3、在WebSecurityConfig中声明九、实现基于JDBC的动态权限控制1、权限拦截器2、安全元数据源FilterInvocationSecurityMetadataSource3、访问决策管理器AccessDecisionManager4、在WebSecurityConfig中声明十、最终的WebSecurityConfig配置十一、结束语前言
关于Spring Security的概念部分本文不进行赘述,本文主要针对于对Spring Security以及Springboot有一定了解的小伙伴,帮助大家使用Springboot + Spring Security 实现一个前后端分离登录认证的过程。
文章会一步一步循序渐进的带大家敲一遍代码。最终的代码请看最后。
代码中我用到了插件lombok来生成实体的getter/setter,如果不想装插件请自己补全getter/setter
本文主要的功能
1、前后端分离用户登录认证
2、基于RBAC(角色)的权限控制
一、准备工作
1、统一错误码枚举
/** * @Author: Hutengfei * @Description: 返回码定义 * 规定: * #1表示成功 * #1001~1999 区间表示参数错误 * #2001~2999 区间表示用户错误 * #3001~3999 区间表示接口异常 * @Date Create in 2019/7/22 19:28 */public enum ResultCode { /* 成功 */ SUCCESS(200, "成功"), /* 默认失败 */ COMMON_FAIL(999, "失败"), /* 参数错误:1000~1999 */ PARAM_NOT_VALID(1001, "参数无效"), PARAM_IS_BLANK(1002, "参数为空"), PARAM_TYPE_ERROR(1003, "参数类型错误"), PARAM_NOT_COMPLETE(1004, "参数缺失"), /* 用户错误 */ USER_NOT_LOGIN(2001, "用户未登录"), USER_ACCOUNT_EXPIRED(2002, "账号已过期"), USER_CREDENTIALS_ERROR(2003, "密码错误"), USER_CREDENTIALS_EXPIRED(2004, "密码过期"), USER_ACCOUNT_DISABLE(2005, "账号不可用"), USER_ACCOUNT_LOCKED(2006, "账号被锁定"), USER_ACCOUNT_NOT_EXIST(2007, "账号不存在"), USER_ACCOUNT_ALREADY_EXIST(2008, "账号已存在"), USER_ACCOUNT_USE_BY_OTHERS(2009, "账号下线"), /* 业务错误 */ NO_PERMISSION(3001, "没有权限"); private Integer code; private String message; ResultCode(Integer code, String message) { this.code = code; this.message = message; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } /** * 根据code获取message * * @param code * @return */ public static String getMessageByCode(Integer code) { for (ResultCode ele : values()) { if (ele.getCode().equals(code)) { return ele.getMessage(); } } return null; }}2、统一json返回体
/** * @Author: Hutengfei * @Description: 统一返回实体 * @Date Create in 2019/7/22 19:20 */public class JsonResult<T> implements Serializable { private Boolean success; private Integer errorCode; private String errorMsg; private T data; public JsonResult() { } public JsonResult(boolean success) { this.success = success; this.errorCode = success ? ResultCode.SUCCESS.getCode() : ResultCode.COMMON_FAIL.getCode(); this.errorMsg = success ? ResultCode.SUCCESS.getMessage() : ResultCode.COMMON_FAIL.getMessage(); } public JsonResult(boolean success, ResultCode resultEnum) { this.success = success; this.errorCode = success ? ResultCode.SUCCESS.getCode() : (resultEnum == null ? ResultCode.COMMON_FAIL.getCode() : resultEnum.getCode()); this.errorMsg = success ? ResultCode.SUCCESS.getMessage() : (resultEnum == null ? ResultCode.COMMON_FAIL.getMessage() : resultEnum.getMessage()); } public JsonResult(boolean success, T data) { this.success = success; this.errorCode = success ? ResultCode.SUCCESS.getCode() : ResultCode.COMMON_FAIL.getCode(); this.errorMsg = success ? ResultCode.SUCCESS.getMessage() : ResultCode.COMMON_FAIL.getMessage(); this.data = data; } public JsonResult(boolean success, ResultCode resultEnum, T data) { this.success = success; this.errorCode = success ? ResultCode.SUCCESS.getCode() : (resultEnum == null ? ResultCode.COMMON_FAIL.getCode() : resultEnum.getCode()); this.errorMsg = success ? ResultCode.SUCCESS.getMessage() : (resultEnum == null ? ResultCode.COMMON_FAIL.getMessage() : resultEnum.getMessage()); this.data = data; } public Boolean getSuccess() { return success; } public void setSuccess(Boolean success) { this.success = success; } public Integer getErrorCode() { return errorCode; } public void setErrorCode(Integer errorCode) { this.errorCode = errorCode; } public String getErrorMsg() { return errorMsg; } public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } public T getData() { return data; } public void setData(T data) { this.data = data; }}3、返回体构造工具
/** * @Author: Hutengfei * @Description: * @Date Create in 2019/7/22 19:52 */public class ResultTool { public static JsonResult success() { return new JsonResult(true); } public static <T> JsonResult<T> success(T data) { return new JsonResult(true, data); } public static JsonResult fail() { return new JsonResult(false); } public static JsonResult fail(ResultCode resultEnum) { return new JsonResult(false, resultEnum); }}4、pom
<?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.1.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.spring</groupId> <artifactId>security</artifactId> <version>0.0.1-SNAPSHOT</version> <https://www.easck.com/;name>security</name> <description>测试spring-security工程</description> <properties> <java.version>1.8</java.version> <spring.security.version>5.1.6.RELEASE</spring.security.version> <fastjson.version>1.2.46</fastjson.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> <!-- spring-security --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${spring.security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${spring.security.version}</version> </dependency> <!-- Hikari连接池--> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> <exclusions> <!-- 排除 tomcat-jdbc 以使用 HikariCP --> <exclusion> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-jdbc</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <!-- Mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatisplus-spring-boot-starter</artifactId> <version>1.0.5</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plu }); http.addFilterBefore(securityInterceptor, FilterSecurityInterceptor.class);//增加到默认拦截链中十、最终的WebSecurityConfig配置
/** * @Author: Hutengfei * @Description: * @Date Create in 2019/8/28 20:15 */@Configuration@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter { //登录成功处理逻辑 @Autowired CustomizeAuthenticationSuccessHandler authenticationSuccessHandler; //登录失败处理逻辑 @Autowired CustomizeAuthenticationFailureHandler authenticationFailureHandler; //权限拒绝处理逻辑 @Autowired CustomizeAccessDeniedHandler accessDeniedHandler; //匿名用户访问无权限资源时的异常 @Autowired CustomizeAuthenticationEntryPoint authenticationEntryPoint; //会话失效(账号被挤下线)处理逻辑 @Autowired CustomizeSessionInformationExpiredStrategy sessionInformationExpiredStrategy; //登出成功处理逻辑 @Autowired CustomizeLogoutSuccessHandler logoutSuccessHandler; //访问决策管理器 @Autowired CustomizeAccessDecisionManager accessDecisionManager; //实现权限拦截 @Autowired CustomizeFilterInvocationSecurityMetadataSource securityMetadataSource; @Autowired private CustomizeAbstractSecurityInterceptor securityInterceptor; @Bean public UserDetailsService userDetailsService() { //获取用户账号密码及权限信息 return new UserDetailsServiceImpl(); } @Bean public BCryptPasswordEncoder passwordEncoder() { // 设置默认的加密方式(强hash方式加密) return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()); } @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().csrf().disable(); http.authorizeRequests(). //antMatchers("/getUser").hasAuthority("query_user"). //antMatchers("/**").fullyAuthenticated(). withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O o) { o.setAccessDecisionManager(accessDecisionManager);//决策管理器 o.setSecurityMetadataSource(securityMetadataSource);//安全元数据源 return o; } }). //登出 and().logout(). permitAll().//允许所有用户 logoutSuccessHandler(logoutSuccessHandler).//登出成功处理逻辑 deleteCookies("JSESSIONID").//登出之后删除cookie //登入 and().formLogin(). permitAll().//允许所有用户 successHandler(authenticationSuccessHandler).//登录成功处理逻辑 failureHandler(authenticationFailureHandler).//登录失败处理逻辑 //异常处理(权限拒绝、登录失效等) and().exceptionHandling(). accessDeniedHandler(accessDeniedHandler).//权限拒绝处理逻辑 authenticationEntryPoint(authenticationEntryPoint).//匿名用户访问无权限资源时的异常处理 //会话管理 and().sessionManagement(). maximumSessions(1).//同一账号同时登录最大用户数 expiredSessionStrategy(sessionInformationExpiredStrategy);//会话失效(账号被挤下线)处理逻辑 http.addFilterBefore(securityInterceptor, FilterSecurityInterceptor.class); }}十一、结束语
到现在为止本文就基本结束了,在本文中我们利用Springboot+Spring security实现了前后端分离的用户登录认证和动态的权限访问控制。
最后附上github地址:
github
暂时禁止评论













闽公网安备 35020302000061号