SpringSecurity04 利用JPA和SpringSecurity實現先後端分離的認證和受權

 

 

1 環境搭建

  1.1 環境說明

    JDK:1.8html

    MAVEN:3.5前端

    SpringBoot:2.0.4java

    SpringSecurity:5.0.7mysql

    IDEA:2017.02旗艦版git

  1.2 環境搭建

    建立一個SpringBoot項目,引入相關依賴:WEB、JPA、MYSQL、SpringSecurity、lombok、devtoolsweb

<?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

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接口

    該接口主要用於測試用的sql

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, 用戶密碼在啓動應用時會打印輸出到控制檯apache

    技巧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 本博文源代碼

  點擊獲取

 

 

 

相關文章
相關標籤/搜索