SpringBoot学习要点记录(八)----集成 Spring Security+SpringDataJpa

一、环境介绍

本篇采用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账号登录):

无权限

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值