<
>

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

暂时禁止评论

微信扫一扫

易采站长站微信账号