一、环境介绍
本篇采用SpringBoot 2.2.5 + Spring Security 5.2.2+springDatajpa
参考博客(感谢博主精彩总结):SpringBoot集成Spring Security(1)
他采用的是Mybatis,如果采用Mybaits可以看他的博客
我的代码:链接:https://pan.baidu.com/s/1eT7UO_Biogwv0FTGsRiVVQ
提取码:uoms
二、准备工作(建议直接下载源码复制粘贴)
2.1 pom.xml
<?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.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example.security</groupId>
<artifactId>boot_security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>boot_security</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--web-->
<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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!--jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.2 application.yml
#端口
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/boot_security?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
jpa:
#显示sql
show-sql: true
hibernate:
#自动建表
#ddl-auto: update
2.3 数据库sql
/*
Navicat Premium Data Transfer
Source Server : 本地mysql5.7
Source Server Type : MySQL
Source Server Version : 50724
Source Host : localhost:3306
Source Schema : boot_security
Target Server Type : MySQL
Target Server Version : 50724
File Encoding : 65001
Date: 26/03/2020 11:26:10
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'ROLE_ADMIN');
INSERT INTO `sys_role` VALUES (2, 'ROLE_USER');
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'admin', '123');
INSERT INTO `sys_user` VALUES (2, 'zhangsan', '123');
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`user_id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
PRIMARY KEY (`role_id`, `user_id`) USING BTREE,
INDEX `FKb40xxfch70f5qnyfw8yme1n1s`(`user_id`) USING BTREE,
CONSTRAINT `FKb40xxfch70f5qnyfw8yme1n1s` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `FKhh52n8vd4ny9ff4x9fb8v65qx` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1);
INSERT INTO `sys_user_role` VALUES (2, 2);
SET FOREIGN_KEY_CHECKS = 1;
注意:如果选择自动建表,且自动建表策略配置为create(每次启动重新建表)时,建表之前会先删除原来表的外键关联关系,因为表还不存在,会报错。可选择配置的为update。自动建表的话建完以后手动插入数据。
三、主要代码
目录结构
3.1 entity
@Data
@Entity
@Table(name = "sys_role")
public class SysRole implements Serializable {
static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column
private String name;
}
@Data
@Entity
@Table(name = "sys_user")
public class SysUser implements Serializable {
static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column
private String name;
@Column
private String password;
@ManyToMany(targetEntity = SysRole.class,cascade = CascadeType.ALL,fetch=FetchType.EAGER)
@JoinTable(name= "sys_user_role"
//joinColumns,当前对象在中间表的外键
,joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "id")}
//inverseJoinColumns,对方对象在中间表的外键
,inverseJoinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "id")})
private Set<SysRole> roles;
}
3.2 dao
public interface SysUserRepository extends JpaRepository<SysUser, Long>, JpaSpecificationExecutor<SysUser> {
// 通过角色名查询
SysUser findByName(String name);
}
public interface SysRoleRepository extends JpaRepository<SysRole, Long>, JpaSpecificationExecutor<SysRole> {
}
3.3 service
@Service
public class SysUserService {
@Autowired
private SysUserRepository sysUserRepository;
public SysUser findByName(String name){
return sysUserRepository.findByName(name);
}
}
这个没暂时没用到,先空着
@Service
public class SysRoleService {
}
3.4 controller
@Controller
public class LoginController {
private Logger logger = LoggerFactory.getLogger(LoginController.class);
@RequestMapping("/")
public String showHome() {
//获取当前登录用户
String name = SecurityContextHolder.getContext().getAuthentication().getName();
logger.info("当前登陆用户:" + name);
return "home.html";
}
@RequestMapping("/login")
public String showLogin() {
return "login.html";
}
/**
* @PreAuthorize 用于判断用户是否有指定权限,没有就不能访问
* @return
*/
@RequestMapping("/admin")
@ResponseBody
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String printAdmin() {
return "如果你看见这句话,说明你有ROLE_ADMIN角色";
}
@RequestMapping("/user")
@ResponseBody
@PreAuthorize("hasRole('ROLE_USER')")
public String printUser() {
return "如果你看见这句话,说明你有ROLE_USER角色";
}
}
四、SpringSecurity配置
4.1 UserDetailsService
自定义MyUserDetailsService
继承UserDetailsService
我们需要重写 loadUserByUsername 方法,参数是用户输入的用户名。返回值是UserDetails,这是一个接口,一般使用它的子类org.springframework.security.core.userdetails.User,它有三个参数,分别是用户名、密码和权限集。
package com.example.security.config;
import com.example.security.entity.SysRole;
import com.example.security.entity.SysUser;
import com.example.security.service.SysRoleService;
import com.example.security.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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.ArrayList;
import java.util.Collection;
import java.util.Set;
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private SysUserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Collection<GrantedAuthority> authorities = new ArrayList<>();
// 从数据库中取出用户信息
SysUser user = userService.findByName(username);
// 判断用户是否存在
if(user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
// 添加权限
Set<SysRole> userRoles = user.getRoles();
for (SysRole sysRole : userRoles) {
authorities.add(new SimpleGrantedAuthority(sysRole.getName()));
}
// 返回UserDetails实现类
return new User(user.getName(), user.getPassword(), authorities);
}
}
4.2 WebSecurityConfig
该类是 Spring Security 的配置类,该类的三个注解分别是标识该类是配置类、开启 Security 服务、开启全局 Securtiy 注解。
将我们自定义的 userDetailsService
注入进来,在 configure() 方法中使用 auth.userDetailsService() 方法替换掉默认的 userDetailsService。
package com.example.security.config;
import org.springframework.beans.factory.annotation.Autowired;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService myUserDetailsService;
/**
* 用来配置用户签名服务,主要是user-details机制,你还可以给予用户赋予角色
* @param auth 签名管理器构造器,用于构建用户具体权限控制
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(charSequence.toString());
}
});
}
/**
* 用来配置拦截保护的请求,比如什么请求放行,什么请求需要验证
* @param http 安全请求对象
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 如果有允许匿名的url,填在下面
// .antMatchers().permitAll()
.anyRequest().authenticated()
.and()
// 设置登陆页
.formLogin().loginPage("/login")
// 设置登陆成功页
.defaultSuccessUrl("/").permitAll()
// 自定义登陆用户名和密码参数,默认为username和password
// .usernameParameter("username")
// .passwordParameter("password")
.and()
.logout().permitAll();
// 关闭CSRF跨域
http.csrf().disable();
}
/**
* 用来配置 Filter 链
* @param web Spring Web Securty 对象
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
// 设置拦截忽略文件夹,可以对静态资源放行
web.ignoring().antMatchers("/css/**", "/js/**");
}
}
五、启动测试
密码错误情况:
登录成功情况:
检测权限(zhangsan账号登录):
无权限