zoukankan      html  css  js  c++  java
  • SpringSecurity04 利用JPA和SpringSecurity实现前后端分离的认证和授权

    1 环境搭建

      1.1 环境说明

        JDK:1.8

        MAVEN:3.5

        SpringBoot:2.0.4

        SpringSecurity:5.0.7

        IDEA:2017.02旗舰版

      1.2 环境搭建

        创建一个SpringBoot项目,引入相关依赖:WEB、JPA、MYSQL、SpringSecurity、lombok、devtools

    <?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.xunyji</groupId>
        <artifactId>springsecurity03</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <name>springsecurity03</name>
        <description>Demo project for Spring Boot</description>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.4.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    
    </project>
    pom.xml

      1.3 MySQL连接信息配置

        在yml配置文件中配置MySQL相关的配置,参考文档

    spring:
      datasource:
        url: jdbc:mysql://127.0.0.1/testdemo?characterEncoding=utf-8&useSSL=false
        username: root
        password: 182838
    
    
      jpa:
        properties:
          hibernate:
            format_sql: true
            show_sql: true
    application.yml

      1.4 创建一个Restful接口

        该接口主要用于测试用的

    package com.xunyji.springsecurity03.controller;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author 王杨帅
     * @create 2018-09-08 21:51
     * @desc
     **/
    @RestController
    @RequestMapping(value = "/test")
    @Slf4j
    public class TestController {
    
        @GetMapping(value = "/home")
        public String home() {
            String info = "寻渝记主页面";
            log.info(info);
            return info;
        }
    
    }
    TestController.java

      1.5 启动应用

        技巧01:SpringBoot集成了SpringSecurity后,默认会对除了 /login 的所有请求进行拦截认证,所以我们启动应用后访问 /test/home 时会自动重定向到 /login 去进行登录录入

        技巧02:SpringSecurity默认提供了一个用户,用户名是 user, 用户密码在启动应用时会打印输出到控制台

        技巧03:SpringSecurity默认在登录成功后会自动跳转到之前访问的请求【PS: 这种方式在前后端分离的项目中并不适用,所以需要进行登录成功和失败来接,让登录成功和失败后返回JSON信息给前端,具体怎么跳转有前端进行控制】

    2 基于内存的认证和授权

      待更新......2018年9月8日22:01:11

      参考文档

    3 基于JPA实现SpringSecurity的认证和授权

      3.1 创建数据库表

        用户表:用于存放用户的相关信息

        角色表:用于存放角色信息【PS: 角色表插入数据时必须全部大写,而且要以 ROLE_ 开头】

        用户角色表:用户存放用户和角色的关联信息【PS: 用户和角色是多对多的关系】

    /*
    Navicat MySQL Data Transfer
    
    Source Server         : mysql5.4
    Source Server Version : 50540
    Source Host           : localhost:3306
    Source Database       : testdemo
    
    Target Server Type    : MYSQL
    Target Server Version : 50540
    File Encoding         : 65001
    
    Date: 2018-09-08 21:30:53
    */
    
    SET FOREIGN_KEY_CHECKS=0;
    
    -- ----------------------------
    -- Table structure for `security_authority`
    -- ----------------------------
    DROP TABLE IF EXISTS `security_authority`;
    CREATE TABLE `security_authority` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `authority` varchar(255) NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
    
    -- ----------------------------
    -- Records of security_authority
    -- ----------------------------
    INSERT INTO `security_authority` VALUES ('1', 'ROLE_ADMIN');
    INSERT INTO `security_authority` VALUES ('2', 'ROLE_USER');
    INSERT INTO `security_authority` VALUES ('3', 'ROLE_BOSS');
    
    -- ----------------------------
    -- Table structure for `security_user`
    -- ----------------------------
    DROP TABLE IF EXISTS `security_user`;
    CREATE TABLE `security_user` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `username` varchar(255) NOT NULL,
      `password` varchar(255) NOT NULL,
      `email` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
    
    -- ----------------------------
    -- Records of security_user
    -- ----------------------------
    INSERT INTO `security_user` VALUES ('1', 'admin', '$2a$10$AC0nr/qvRHHn2YNsKNbH/.X9f0dHhIZX0457mwFPBJ6jS2/Tcmu3S', '412444321@qq.com');
    INSERT INTO `security_user` VALUES ('2', 'wys', '$2a$10$YH9HijmebwcDfTdbx5ho2OlJ6.zewxufvCrnioVGI5PcXFsqNtCd6', '41fasd321@qq.com');
    INSERT INTO `security_user` VALUES ('3', 'boss', '$2a$10$iZ/467THEoA7E/MjOA6iJeBpZJpebIfRzvFbZhKNKwyyFfBypmQTi', 'asdfa@qq.com');
    
    -- ----------------------------
    -- Table structure for `security_user_role`
    -- ----------------------------
    DROP TABLE IF EXISTS `security_user_role`;
    CREATE TABLE `security_user_role` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `user_id` int(11) NOT NULL,
      `role_id` int(11) NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
    
    -- ----------------------------
    -- Records of security_user_role
    -- ----------------------------
    INSERT INTO `security_user_role` VALUES ('1', '1', '1');
    INSERT INTO `security_user_role` VALUES ('2', '2', '2');
    INSERT INTO `security_user_role` VALUES ('3', '3', '1');
    INSERT INTO `security_user_role` VALUES ('4', '3', '2');
    INSERT INTO `security_user_role` VALUES ('5', '3', '3');
    INSERT INTO `security_user_role` VALUES ('6', '1', '2');
    user_role.sql

      3.2 创建实体类

        技巧01:利用IDEA批量创建实体类,参考文档

      3.4 创建对应的Repository

        每个实体类创建一个Repository

        技巧01:利用Repository添加用户,添加用户时必须利用PasswordEncoder对密码进行加密

        技巧02:添加用户前需要创建一个PasswordEncoder的Bean

      3.5  创建服务层

        技巧01:本博文只创建了UserService的服务层,因为仅仅是实现认证和授权的功能

        技巧02:如果需要实现自定义的认证逻辑,就必须让UserService实现UserDetailsService接口,所有认证相关的逻辑都在loadUserByUsername方法中进行

           技巧03:loadUserByUsername方法中主要是根据用户名查找用户信息以及该用户的角色信息,该方法的返回值是SpringSecurity提供的User对象

    package com.xunyji.springsecurity02.service;
    
    import com.xunyji.springsecurity02.pojo.dataobject.SecurityAuthorityDO;
    import com.xunyji.springsecurity02.pojo.dataobject.SecurityUserDO;
    import com.xunyji.springsecurity02.pojo.dataobject.SecurityUserRoleDO;
    import com.xunyji.springsecurity02.repository.AuthorityRepository;
    import com.xunyji.springsecurity02.repository.UserRepository;
    import com.xunyji.springsecurity02.repository.UserRoleRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.authority.AuthorityUtils;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.User;
    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.List;
    import java.util.stream.Collectors;
    
    /**
     * @author 王杨帅
     * @create 2018-09-08 20:46
     * @desc
     **/
    @Service
    public class UserService implements UserDetailsService {
    
        @Autowired
        private UserRepository userRepository;
    
        @Autowired
        private UserRoleRepository userRoleRepository;
    
        @Autowired
        private AuthorityRepository authorityRepository;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
            // 根据用户名查找用户信息
            SecurityUserDO byUsername = userRepository.findByUsername(username);
            System.out.println(byUsername);
    
            // 根据用户ID查找用户角色关联信息
            List<SecurityUserRoleDO> byUserId = userRoleRepository.findByUserId(byUsername.getId());
            byUserId.stream().forEach(System.out::println);
    
            // 获取该用户对应的所有角色的ID列表
            List<Integer> collect = byUserId.stream()
                    .map(info -> info.getRoleId())
                    .collect(Collectors.toList());
            collect.stream().forEach(System.out::println);
    
            // 获取该用户对应的所有角色信息
            List<SecurityAuthorityDO> allById = authorityRepository.findAllById(collect);
            allById.stream().forEach(System.out::println);
    
            List<SimpleGrantedAuthority> authorityList = allById.stream()
                    .map(info -> new SimpleGrantedAuthority(info.getAuthority()))
                    .collect(Collectors.toList());
            authorityList.stream().forEach(System.out::println);
    
            // 封装该用户的用户名、密码、角色
            return new User(byUsername.getUsername(), byUsername.getPassword(), authorityList);
        }
    }
    UserService.java

      3.6 SpringSecurity相关配置

        3.6.1 认证成功拦截器

          认证成功后默认是跳转到之前的请求API,可以通过认证成功拦截器让认证成功后返回一些JSON信息

    package com.xunyji.springsecurity02.config;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * @author 王杨帅
     * @create 2018-09-08 15:19
     * @desc
     **/
    @Component
    @Slf4j
    public class XiangXuAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    
        @Autowired
        private ObjectMapper objectMapper;
    
        @Override
        public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
            log.info("登录成功");
            httpServletResponse.setContentType("application/json;charset=UTF-8");
            httpServletResponse.getWriter()
                                    .write(objectMapper.writeValueAsString(authentication));
        }
    }
    XiangXuAuthenticationSuccessHandler.java

        3.6.2 认证失败拦截器

          认证失败后默认是跳转到 /login ,可以通过认证失败拦截器让认证失败后返回一些JSON信息

    package com.xunyji.springsecurity02.config;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.authentication.AuthenticationFailureHandler;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * @author 王杨帅
     * @create 2018-09-08 15:22
     * @desc
     **/
    @Component
    @Slf4j
    public class XiangXuAuthenticationFailureHandler implements AuthenticationFailureHandler {
    
        @Autowired
        private ObjectMapper objectMapper;
    
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                            AuthenticationException exception) throws IOException, ServletException {
            log.info("登陆失败");
    
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(exception));
        }
    }
    XiangXuAuthenticationFailureHandler.java

         3.6.2 自定义认证授权配置

    package com.xunyji.springsecurity02.config;
    
    import com.xunyji.springsecurity02.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.authentication.dao.DaoAuthenticationProvider;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.authentication.AuthenticationFailureHandler;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    
    /**
     * @author 王杨帅
     * @create 2018-09-08 20:13
     * @desc
     **/
    @Configuration
    public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        private UserService userService;
    
        @Autowired
        private AuthenticationFailureHandler xiangXuAuthenticationFailureHandler;
    
        @Autowired
        private AuthenticationSuccessHandler xiangXuAuthenticationSuccessHandler;
    
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.authenticationProvider(authenticationProvider()); // 添加自定义的认证逻辑
        }
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            super.configure(web);
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .formLogin()
                    .loginProcessingUrl("/furyLogin") // 提交登录信息的API
                    .usernameParameter("name") // 登录名
                    .passwordParameter("pwd") // 登录密码
                    .successHandler(xiangXuAuthenticationSuccessHandler) // 登录成功处理器
                    .failureHandler(xiangXuAuthenticationFailureHandler) // 登录失败处理器
                .and().authorizeRequests()
                    .antMatchers("/test/home").permitAll() // 该api不需要授权
                    .anyRequest().authenticated() // 剩余都需要授权
                .and()
                    .logout().permitAll() // 登出API不需要授权
                .and()
                    .csrf().disable();
    
        }
    
        /**
         * 創建PsswordEncoder對應的Bean
         * @return
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        /**
         * 创建认证提供者Bean
         * 技巧01:DaoAuthenticationProvider是SpringSecurity提供的AuthenticationProvider实现类
         * @return
         */
        @Bean
        public DaoAuthenticationProvider authenticationProvider() {
            DaoAuthenticationProvider authProvider
                    = new DaoAuthenticationProvider(); // 创建DaoAuthenticationProvider实例
            authProvider.setUserDetailsService(userService); // 将自定义的认证逻辑添加到DaoAuthenticationProvider
            authProvider.setPasswordEncoder(passwordEncoder); // 设置自定义的密码加密
            return authProvider;
        }
    
    }
    MySpringSecurityConfig.java

      3,7 编写控制类

    package com.xunyji.springsecurity02.controller;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.security.access.prepost.PreAuthorize;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author 王杨帅
     * @create 2018-09-08 21:17
     * @desc
     **/
    @RestController
    @RequestMapping(value = "/test")
    @Slf4j
    @EnableGlobalMethodSecurity(prePostEnabled = true) // 开启授权
    public class TestController {
    
        @GetMapping(value = "/home")
        public String home() {
            String info = "寻渝记主页面";
            log.info(info);
            return info;
        }
    
        @PreAuthorize("hasRole('ROLE_BOSS')") // 拥有ROLE_BOSS角色的用户可以访问
        @GetMapping(value = "/boss")
        public String boss() {
            String info = "拥有ROLE_BOSS权限的才可以进入";
            log.info(info);
            return info;
        }
    
        @PreAuthorize("hasRole('ROLE_BOSS') OR hasRole('ROLE_ADMIN')")
        @GetMapping(value = "/admin")
        public String admin() {
            String info = "拥有ROLE_ADMIN权限的才可以进入";
            log.info(info);
            return info;
        }
    
        @PreAuthorize("hasRole('ROLE_BOSS') OR hasRole('ROLE_ADMIN') OR hasRole('ROLE_USER')")
        @GetMapping(value = "/user")
        public String user() {
            String info = "拥有ROLE_USER权限的才可以进入";
            log.info(info);
            return info;
        }
    
    }
    TestController.java

      3.8 启动测试

        技巧01:如果没有进行认证前访问需要进行权限的 Restful 服务,就会自动返回一个登录页面【问题:如何返回一个JSON信息呢】

        技巧02:登录认证成功后,如果访问该用户权限之外的 Restful 服务,那么就会提示权限不足【即:403状态码】

    4 本博文源代码

      点击获取

     

  • 相关阅读:
    JavaScript的离线存储——localStorage、sessionStorage以及cookie
    for循环中的异步处理(异步变同步)
    pc网站随鼠标滚动动态出现效果
    layui tab选项卡Hash地址的定位和跳转到指定tab栏
    scroll滚动监听实现animate返回顶部(有坑)
    Vue之使用elementUI的upload上传组件导入csv文件
    element+sortablejs插件实现拖拽排序效果
    超简单的jq图片上传
    取字符串中的汉字的俩种方式
    js获得url地址携带参数
  • 原文地址:https://www.cnblogs.com/NeverCtrl-C/p/7993200.html
Copyright © 2011-2022 走看看