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 ('', 'ROLE_ADMIN');
INSERT INTO `security_authority` VALUES ('', 'ROLE_USER');
INSERT INTO `security_authority` VALUES ('', '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 ('', 'admin', '$2a$10$AC0nr/qvRHHn2YNsKNbH/.X9f0dHhIZX0457mwFPBJ6jS2/Tcmu3S', '412444321@qq.com');
INSERT INTO `security_user` VALUES ('', 'wys', '$2a$10$YH9HijmebwcDfTdbx5ho2OlJ6.zewxufvCrnioVGI5PcXFsqNtCd6', '41fasd321@qq.com');
INSERT INTO `security_user` VALUES ('', '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 ('', '', '');
INSERT INTO `security_user_role` VALUES ('', '', '');
INSERT INTO `security_user_role` VALUES ('', '', '');
INSERT INTO `security_user_role` VALUES ('', '', '');
INSERT INTO `security_user_role` VALUES ('', '', '');
INSERT INTO `security_user_role` VALUES ('', '', '');

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 本博文源代码

  点击获取

SpringSecurity04 利用JPA和SpringSecurity实现前后端分离的认证和授权的更多相关文章

  1. springSecurity + jwt + redis 前后端分离用户认证和授权

    记录一下使用springSecurity搭建用户认证和授权的代码... 技术栈使用springSecurity + redis + JWT + mybatisPlus 部分代码来自:https://b ...

  2. 利用grunt-contrib-connect和grunt-connect-proxy搭建前后端分离的开发环境

    前后端分离这个词一点都不新鲜,完全的前后端分离在岗位协作方面,前端不写任何后台,后台不写任何页面,双方通过接口传递数据完成软件的各个功能实现.此种情况下,前后端的项目都独立开发和独立部署,在开发期间有 ...

  3. 【SpringSecurity系列2】基于SpringSecurity实现前后端分离无状态Rest API的权限控制原理分析

    源码传送门: https://github.com/ningzuoxin/zxning-springsecurity-demos/tree/master/01-springsecurity-state ...

  4. SpringBoot20 集成SpringSecurity02 -> 利用SpringSecurity进行前后端分离的登录验证

    1 SpirngBoot环境搭建 创建一个SpringBoot项目即可,详情参见三少的相关博文 参考博文 -> 点击前往 SpirngBoot项目脚手架 -> 点击前往 2 引入Spirn ...

  5. Spring-Gateway与Spring-Security在前后端分离项目中的实践

    前言 网上貌似webflux这一套的SpringSecurity操作资料貌似很少. 自己研究了一波,记录下来做一点备忘,如果能帮到也在迷惑的人一点点,就更好了. 新项目是前后端分离的项目,前台vue, ...

  6. 【SpringSecurity系列3】基于Spring Webflux集成SpringSecurity实现前后端分离无状态Rest API的权限控制

    源码传送门: https://github.com/ningzuoxin/zxning-springsecurity-demos/tree/master/02-springsecurity-state ...

  7. 【SpringSecurity系列1】基于SpringSecurity实现前后端分离无状态Rest API的权限控制

    源码传送门: https://github.com/ningzuoxin/zxning-springsecurity-demos/tree/master/01-springsecurity-state ...

  8. 如何利用vue和php做前后端分离开发?

    新手上路,前端工程师,刚毕业参加工作两个月,上面让我用vue搭建环境和php工程师一起开发,做前后端分离,然而我只用过简单的vue做一些小组件的经验,完全不知道怎样和php工程师配合,ps: php那 ...

  9. Spring Security + JWT实现前后端分离权限认证

    现在国内前后端很多公司都在使用前后端分离的开发方式,虽然也有很多人并不赞同前后端分离,比如以下这篇博客就很有意思: https://www.aliyun.com/jiaocheng/650661.ht ...

随机推荐

  1. 【leetcode刷题笔记】Longest Valid Parentheses

    Given a string containing just the characters '(' and ')', find the length of the longest valid (wel ...

  2. (vue.js)vue中引用了别的组件 ,如何使this指向Vue对象

    Vue中引用了别的组件 ,如何使this指向Vue对象 今天学习Vue组件传值, 通过创建Vue实例, 广播和监听实现传值, 但是传值之后无法直接将得到的值应用到Vue对象, 因为这相当于引用改了别的 ...

  3. windows8.1下安装msi文件报错

    新安装了win8.1系统体验体验,可是安装msi文件的软件报internal error2502和2503错误,可以换一种安装方式. 不是直接点开安装,如图所示打开命令提示符: 使用msiexec / ...

  4. jupyter- 运维

    jupyter运维常见CLI 查看安装的内核和位置 jupyter kernelspec list anaconda列出所有的环境 conda info -e

  5. MATLAB卷积运算(conv、conv2)解释

    来源:https://www.cnblogs.com/hyb221512/p/9276621.html 1.conv(向量卷积运算) 所谓两个向量卷积,说白了就是多项式乘法.比如:p=[1 2 3], ...

  6. jquery详解图片平滑滚动

    jquery详解图片平滑滚动 随便写了个DOM,没有美观性,见谅 原理: 1.定义两组ul列表放图,第一个ul放5张图,第二个ul为空 2.为什么要用两个ul?因为要用到jQuery的克隆方法clon ...

  7. jQuery - 获取/设置内容和属性

    获得内容 - text().html() 以及 val() 三个简单实用的用于 DOM 操作的 jQuery 方法: text() - 设置或返回所选元素的文本内容 html() - 设置或返回所选元 ...

  8. 【python】关于函数递归使用 return 后,收到数据为 None。

    在写一个辗转相除求最小公因数的程序的时候,突然发现自己不管怎么写(除了两数恰巧可以整除),return 返回的值恒为 none. 代码为此: def gcd(a,b): if a%b==0: retu ...

  9. mysql多位小数字段用decimal类型

    转自http://database.51cto.com/art/201005/201651.htm

  10. AngularJS-指令command

    directive: 匹配模式restrict:'AEMC'默认为A template templateUrl templateCache:把模板缓存起来,共多个指令使用 var myModule = ...