美文网首页
SpringSecurity实现单体应用的认证授权

SpringSecurity实现单体应用的认证授权

作者: 攻城老狮 | 来源:发表于2020-06-27 07:28 被阅读0次

    技术栈:SpringBoot + Mybatis + SpringSecurity
    程序地址:https://github.com/yaokuku123/spring-security
    功能:实现基础的访问控制功能。通过注册在数据库中的用户,角色信息,实现认证和授权的功能

    1. 目录结构

    image-20210224104648367.png

    2. MySQL建表

    2.1 构建MySQL数据库

    /*
    SQLyog Ultimate v12.08 (64 bit)
    MySQL - 8.0.16 : Database - security_authority
    *********************************************************************
    */
    
    
    /*!40101 SET NAMES utf8 */;
    
    /*!40101 SET SQL_MODE=''*/;
    
    /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
    /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
    /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
    /*Table structure for table `sys_permission` */
    
    DROP TABLE IF EXISTS `sys_permission`;
    
    CREATE TABLE `sys_permission` (
      `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
      `permission_NAME` varchar(30) DEFAULT NULL COMMENT '菜单名称',
      `permission_url` varchar(100) DEFAULT NULL COMMENT '菜单地址',
      `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '父菜单id',
      PRIMARY KEY (`ID`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    /*Data for the table `sys_permission` */
    
    /*Table structure for table `sys_role` */
    
    DROP TABLE IF EXISTS `sys_role`;
    
    CREATE TABLE `sys_role` (
      `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
      `ROLE_NAME` varchar(30) DEFAULT NULL COMMENT '角色名称',
      `ROLE_DESC` varchar(60) DEFAULT NULL COMMENT '角色描述',
      PRIMARY KEY (`ID`)
    ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
    
    /*Data for the table `sys_role` */
    
    /*Table structure for table `sys_role_permission` */
    
    DROP TABLE IF EXISTS `sys_role_permission`;
    
    CREATE TABLE `sys_role_permission` (
      `RID` int(11) NOT NULL COMMENT '角色编号',
      `PID` int(11) NOT NULL COMMENT '权限编号',
      PRIMARY KEY (`RID`,`PID`),
      KEY `FK_Reference_12` (`PID`),
      CONSTRAINT `FK_Reference_11` FOREIGN KEY (`RID`) REFERENCES `sys_role` (`ID`),
      CONSTRAINT `FK_Reference_12` FOREIGN KEY (`PID`) REFERENCES `sys_permission` (`ID`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    /*Data for the table `sys_role_permission` */
    
    /*Table structure for table `sys_user` */
    
    DROP TABLE IF EXISTS `sys_user`;
    
    CREATE TABLE `sys_user` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `username` varchar(32) NOT NULL COMMENT '用户名称',
      `password` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
      `status` int(1) DEFAULT '1' COMMENT '1开启0关闭',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
    
    /*Data for the table `sys_user` */
    
    /*Table structure for table `sys_user_role` */
    
    DROP TABLE IF EXISTS `sys_user_role`;
    
    CREATE TABLE `sys_user_role` (
      `UID` int(11) NOT NULL COMMENT '用户编号',
      `RID` int(11) NOT NULL COMMENT '角色编号',
      PRIMARY KEY (`UID`,`RID`),
      KEY `FK_Reference_10` (`RID`),
      CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `sys_role` (`ID`),
      CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `sys_user` (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    /*Data for the table `sys_user_role` */
    
    /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
    /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
    /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
    

    2.2 在MySQL的表中添加几组数据用于测试

    说明:

    • 用户:xiaoming

      密码:199748

      拥有的角色:ROLE_USER,ROLE_PRODUCT

    • 用户:xiaoma

      密码:199748

      拥有的角色:ROLE_USER,ROLE_ORDER

    # sys_user表 password使用加密的方式存储 两者的密码明文为 199748
    +----+----------+--------------------------------------------------------------+--------+
    | id | username | password                                                     | status |
    +----+----------+--------------------------------------------------------------+--------+
    |  4 | xiaoming | $2a$10$9fSu8H/o/qKhRYm8N3IrGePdu5Kj3QNujaW5whHGyoi8ta0Bj4SSG |      1 |
    |  5 | xiaoma   | $2a$10$NJkRs/2AoD1iHlLNe4LwPu8M1ZvmVp4lsCD0QEqCoaRg1Jn2AG2hu |      1 |
    +----+----------+--------------------------------------------------------------+--------+
    
    # sys_role表
    +----+--------------+--------------+
    | ID | ROLE_NAME    | ROLE_DESC    |
    +----+--------------+--------------+
    |  6 | ROLE_USER    | Basic Role   |
    |  7 | ROLE_PRODUCT | Product Role |
    |  8 | ROLE_ORDER   | Order Role   |
    |  9 | ROLE_ADMIN   | Root         |
    +----+--------------+--------------+
    
    # sys_user_role表
    +-----+-----+
    | UID | RID |
    +-----+-----+
    |   4 |   6 |
    |   5 |   6 |
    |   4 |   7 |
    |   5 |   8 |
    +-----+-----+
    

    3. 页面部分说明

    3.1 templates文件夹存放使用Thymeleaf模板引擎的动态资源文件

    1. index.html,默认登录认证成功后的页面
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    
    <head>
        <meta charset="UTF-8"/>
        <title>Spring Security</title>
    </head>
    
    <body>
       <h1>Success</h1>
       <a th:href="@{/product}">产品资源</a><br>
       <a th:href="@{/order}">订单资源</a>
       <form th:action="@{/logout}" method="post">
           <input type="submit" value="注销"/>
       </form>
    </body>
    
    </html>
    
    1. product.html,产品资源文件,授权给拥有ROLE_PRODUCT角色的用户
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    
    <head>
        <meta charset="UTF-8"/>
        <title>product</title>
    </head>
    
    <body class="container">
        <h1 th:text="产品资源">product</h1>
        <a th:href="@{/}">返回</a>
    </body>
    
    </html>
    
    1. order.html,订单资源文件,授权给拥有ROLE_ORDER角色的用户
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    
    <head>
        <meta charset="UTF-8"/>
        <title>product</title>
    </head>
    
    <body class="container">
        <h1 th:text="订单资源">order</h1>
        <a th:href="@{/}">返回</a>
    </body>
    
    </html>
    

    3.2 static文件夹中存放的静态资源文件

    1. 403.html 403错误页面,用于处理来自403权限不足错误的请求
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>403</title>
    </head>
    <body>
        <h1>403 Error</h1>
        <a href="/">返回</a>
    </body>
    </html>
    
    1. 500.html 500错误页面,用于简单处理其他错误的页面
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>500</title>
    </head>
    <body>
        <h1>500 Error</h1>
        <a href="/">返回</a>
    </body>
    </html>
    

    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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.yqj</groupId>
        <artifactId>springboot-springsecurity</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <java.version>1.8</java.version>
        </properties>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.6.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <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>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.6</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.2</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

    5. yaml文件完成Spring的相关配置

    application.yaml文件

    server:
      port: 8080
    
    spring:
      datasource:
        username: root
        password: 199748
        url: jdbc:mysql:///security_authority
        driver-class-name: com.mysql.jdbc.Driver
    
    mybatis:
      type-aliases-package: com.yqj.domain
      configuration:
        map-underscore-to-camel-case: true
    
    logging:
      level:
        com.yqj: debug
    

    6. 业务逻辑相关内容的编写

    6.1 SpringBoot启动类

    SecurityApplication文件

    package com.yqj;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * Copyright(C),2019-2021,XXX公司
     * FileName: SecurityApplication
     * Author: yaoqijun
     * Date: 2021/2/24 10:13
     */
    @SpringBootApplication
    @MapperScan("com.yqj.mapper")
    public class SecurityApplication {
        public static void main(String[] args) {
            SpringApplication.run(SecurityApplication.class,args);
        }
    }
    

    6.2 用户和角色类 com.yqj.springsecurity.domain

    1. SysUser用户类
    package com.yqj.domain;
    
    import com.fasterxml.jackson.annotation.JsonIgnore;
    import lombok.Data;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import java.util.Collection;
    import java.util.List;
    
    /**
     * Copyright(C),2019-2021,XXX公司
     * FileName: SysUser
     * Author: yaoqijun
     * Date: 2021/2/24 10:14
     */
    @Data
    public class SysUser implements UserDetails {
    
        private Integer id;
        private String username;
        private String password;
        private List<SysRole> roles;
    
        @JsonIgnore
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return roles;
        }
    
        @Override
        public String getPassword() {
            return password;
        }
    
        @Override
        public String getUsername() {
            return username;
        }
    
        @JsonIgnore
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @JsonIgnore
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @JsonIgnore
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @JsonIgnore
        @Override
        public boolean isEnabled() {
            return true;
        }
    }
    
    1. SysRole角色类
    package com.yqj.domain;
    
    import com.fasterxml.jackson.annotation.JsonIgnore;
    import lombok.Data;
    import org.springframework.security.core.GrantedAuthority;
    
    /**
     * Copyright(C),2019-2021,XXX公司
     * FileName: SysRole
     * Author: yaoqijun
     * Date: 2021/2/24 10:15
     */
    @Data
    public class SysRole implements GrantedAuthority {
    
        private Integer id;
        private String roleName;
        private String roleDesc;
    
        @JsonIgnore
        @Override
        public String getAuthority() {
            return roleName;
        }
    }
    

    6.3 数据库访问层 com.yqj.springsecurity.mapper

    1. UserMapper,用户访问层接口
    package com.yqj.mapper;
    
    import com.yqj.domain.SysUser;
    import org.apache.ibatis.annotations.Many;
    import org.apache.ibatis.annotations.Result;
    import org.apache.ibatis.annotations.Results;
    import org.apache.ibatis.annotations.Select;
    
    import java.util.List;
    
    /**
     * Copyright(C),2019-2021,XXX公司
     * FileName: UserMapper
     * Author: yaoqijun
     * Date: 2021/2/24 10:17
     */
    public interface UserMapper {
    
        @Select("select * from sys_user where username=#{username}")
        @Results({
                @Result(id = true, property = "id", column = "id"),
                @Result(property = "roles", column = "id", javaType = List.class,
                        many = @Many(select = "com.yqj.mapper.RoleMapper.findByUid"))
        })
        public SysUser findByName(String username);
    }
    
    1. RoleMapper,角色访问层接口
    package com.yqj.mapper;
    
    import com.yqj.domain.SysRole;
    import org.apache.ibatis.annotations.Select;
    
    import java.util.List;
    
    /**
     * Copyright(C),2019-2021,XXX公司
     * FileName: RoleMapper
     * Author: yaoqijun
     * Date: 2021/2/24 10:18
     */
    public interface RoleMapper {
    
        @Select(" select r.id,r.role_name roleName,r.role_desc roleDesc " +
                " from sys_role r,sys_user_role ur " +
                " where r.id=ur.rid and ur.uid=#{uid} ")
        public List<SysRole> findByUid(Integer uid);
    }
    

    6.4 服务层 com.yqj.springsecurity.service

    1. UserService,继承SpringSecurity中的用于认证的类UserDetailsService
    package com.yqj.service;
    
    import org.springframework.security.core.userdetails.UserDetailsService;
    
    /**
     * Copyright(C),2019-2021,XXX公司
     * FileName: UserService
     * Author: yaoqijun
     * Date: 2021/2/24 10:18
     */
    public interface UserService extends UserDetailsService {
    }
    
    1. UserServiceImpl,接口的实现类
    package com.yqj.service.impl;
    
    import com.yqj.mapper.UserMapper;
    import com.yqj.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    /**
     * Copyright(C),2019-2021,XXX公司
     * FileName: UserServiceImpl
     * Author: yaoqijun
     * Date: 2021/2/24 10:19
     */
    @Service
    @Transactional
    public class UserServiceImpl implements UserService {
    
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            return userMapper.findByName(username);
        }
    }
    
    

    6.5 控制层 com.yqj.springsecurity.controller

    SecurityController,用于对请求路径进行转发,从而可以访问由模板引擎渲染的动态资源

    package com.yqj.controller;
    
    import org.springframework.security.access.annotation.Secured;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    /**
     * Copyright(C),2019-2021,XXX公司
     * FileName: SecurityController
     * Author: yaoqijun
     * Date: 2021/2/24 10:20
     */
    @Controller
    public class SecurityController {
    
        @RequestMapping("/")
        public String login() {
            return "index";
        }
    
        @Secured("ROLE_PRODUCT")
        @RequestMapping("/product")
        public String product() {
            return "product";
        }
    
        @Secured("ROLE_ORDER")
        @RequestMapping("/order")
        public String learning() {
            return "order";
        }
    
    }
    

    6.6 配置类

    1. SecurityConfig,配置SpringSecurity相关内容
    package com.yqj.config;
    
    import com.yqj.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    
    /**
     * Copyright(C),2019-2021,XXX公司
     * FileName: SecurityConfig
     * Author: yaoqijun
     * Date: 2021/2/24 10:21
     */
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(securedEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private UserService userService;
    
        @Bean
        public BCryptPasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/**").hasAnyRole("USER","ADMIN")
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginProcessingUrl("/login")
                    .successForwardUrl("/")
                    .permitAll()
                    .and()
                    .logout()
                    .logoutUrl("/logout")
                    .invalidateHttpSession(true)
                    .permitAll()
                    .and()
                    .csrf().disable();
        }
    }
    
    1. ControllerExceptionHandler,用于处理错误请求的情况
    package com.yqj.controller;
    
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    
    
    /**
     * Copyright(C),2019-2021,XXX公司
     * FileName: ControllerExceptionHandler
     * Author: yaoqijun
     * Date: 2021/2/24 10:26
     */
    @ControllerAdvice
    public class ControllerExceptionHandler {
    
        @ExceptionHandler(RuntimeException.class)
        public String handlerException(RuntimeException e){
            if(e instanceof AccessDeniedException){
                //重定向到静态页面
                return "redirect:/403.html";
            }else {
                return "redirect:/500.html";
            }
        }
    }
    

    7 效果说明

    • 使用xiaoming认证成功登录后,可以成功访问产品资源,但是无法访问订单资源
    • 使用xiaoma认证成功登录后,可以成功访问订单资源,但是无法访问产品资源

    相关文章

      网友评论

          本文标题:SpringSecurity实现单体应用的认证授权

          本文链接:https://www.haomeiwen.com/subject/sdqmfktx.html