SpringSecurity整合h2及JPA
文章目录
一、pom导入,将需要的maven包导入,初始化环境
<?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.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>SecurityLearn</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SecurityLearn</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
//导入h2
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
</dependency>
//导入jpa
<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.zalando</groupId>
<artifactId>problem-spring-web</artifactId>
<version>0.28.0-RC.0</version>
</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>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
二、配置环境yaml
server:
port: 8080
//输出配置
logging:
pattern:
console: '%clr(%d{E HH:mm:ss.SSS}){blue} %clr(%-5p) %clr(${PID}){faint} %clr(---){faint} %clr([%8.15t]){cyan} %clr(%-40.40logger{0}){blue} %clr(:){red} %clr(%m){faint}%n'
level:
org.springframework.security: DEBUG
//配置database数据源
spring:
datasource:
username: sa
password: root
url: jdbc:h2:mem:test;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;DB_CLOSE_DELAY=-1
driver-class-name: org.h2.Driver
h2:
console:
enabled: true
path: /h2-console
settings:
trace: false
web-allow-others: false
//配置jpa
jpa:
database: h2
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: update
generate-ddl: true
defer-datasource-initialization: true//这一步不可少,jpa自动创建表,不加这个将会导致data.sql无法找到表
//创建data.sql实现数据自动加载,springboot在初始化的时候会自定义创建表,这里的data.sql会自动导入数据。
insert into users(id, username, `name`, mobile, password_hash, enabled, account_non_expired, account_non_locked, credentials_non_expired, email)
values (1, 'user', 'Zhang San', '13012341234', '{bcrypt}$2a$10$jhS817qUHgOR4uQSoEBRxO58.rZ1dBCmCTjG8PeuQAX4eISf.zowm', 1, 1, 1, 1, 'zhangsan@local.dev'),
(2, 'old_user', 'Li Si', '13812341234', '{SHA-1}7ce0359f12857f2a90c7de465f40a95f01cb5da9', 1, 1, 1, 1, 'lisi@local.dev');
insert into roles(id, role_name) values (1, 'ROLE_USER'), (2, 'ROLE_ADMIN');
insert into users_roles(user_id, role_id) values (1, 1), (1, 2), (2, 1);
三、创建UserDetails以及UserDetailsService
-
UserDetails是用户登录的角色类,实际上是一个接口,这里我们自定义创建UserDetails作为我们的验证角色。
-
通常的场景下用户是具备一系列属性约束的,这就是UserDetails。
-
从数据存储种根据用户名查找到用户,是由UserDetailsService定义的。
//用户user表
package com.example.securitylearn.domain;
import com.example.securitylearn.domain.Role;
import lombok.*;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Set;
@AllArgsConstructor
@NoArgsConstructor
@Data
@With
@Builder
@Entity
@Table(name = "users")
public class User implements UserDetails,Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
@Column(name = "password_hash",nullable = false)
private String password;
private String mobile;
private String email;
private String name;
@Column(name = "account_non_expired",nullable = false)
private boolean accountNonExpired;
@Column(name = "account_non_locked",nullable = false)
private boolean accountNonLocked;
@Column(name = "credentials_non_expired",nullable = false)
private boolean credentialsNonExpired;
private boolean enabled;
@ManyToMany
@Fetch(FetchMode.JOIN)
@JoinTable(name = "users_roles",
joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "id")})
private Set<Role> authorities;
}
//角色权限表
package com.example.securitylearn.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import javax.persistence.*;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "roles")
public class Role implements GrantedAuthority , Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "role_name",nullable = false)
private String authority;
}
//创建UserRepo接口实现对数据库的查找,这里创建find方法是根据用户名查找user表的数据。
package com.example.securitylearn.repository;
import com.example.securitylearn.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepo extends JpaRepository<User, Long> {
Optional<User> findOptionByUsername(String name);
}
//jap针对role表的操作
package com.example.securitylearn.repository;
import com.example.securitylearn.domain.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface RoleRepo extends JpaRepository<Role,Long> {
}
//创建自定义的UserDetailsServiceImpl
package com.example.securitylearn.security.userdetail;
import com.example.securitylearn.domain.Role;
import com.example.securitylearn.repository.RoleRepo;
import com.example.securitylearn.repository.UserRepo;
import lombok.RequiredArgsConstructor;
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;
@RequiredArgsConstructor
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepo userRepo;
private final RoleRepo roleRepo;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepo.findOptionByUsername(username)
.orElseThrow(()->new UsernameNotFoundException(username+"用户名未找到"));
}
}
//创建密码自动更新的接口UserDetailsPasswordServiceImpl
package com.example.securitylearn.security.userdetail;
import com.example.securitylearn.repository.UserRepo;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.stereotype.Service;
@RequiredArgsConstructor
@Service
public class UserDetailsPasswordServiceImpl implements UserDetailsPasswordService {
private final UserRepo userRepo;
@Override
public UserDetails updatePassword(UserDetails userDetails, String newPassword) {
return userRepo.findOptionByUsername(userDetails.getUsername())
.map(user -> {
System.out.println("更新了"+newPassword);
return (UserDetails) userRepo.save(user.withPassword(newPassword));
}
).orElse(userDetails);
}
}
四、配置Security
package com.example.securitylearn.config;
import com.example.securitylearn.security.userdetail.UserDetailsPasswordServiceImpl;
import com.example.securitylearn.security.userdetail.UserDetailsServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.Customizer;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;
import javax.sql.DataSource;
import java.util.Map;
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity(debug = true)
@Import(SecurityProblemSupport.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final SecurityProblemSupport securityProblemSupport;
private final UserDetailsServiceImpl userDetailsService;
private final UserDetailsPasswordServiceImpl userDetailsPasswordServiceImpl;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.httpBasic(Customizer.withDefaults()) //httpBasic是由http协议定义的最基础的认证方式
.formLogin(Customizer.withDefaults()) //默认登录
.authorizeRequests(req -> req.mvcMatchers("/api/**").authenticated());//请求认证,访问api下的所有接口都会需要认证
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)//配置数据源
.passwordEncoder(passwordEncoder())//密码解析器,即security通过此密码方式加密
.userDetailsPasswordManager(userDetailsPasswordServiceImpl);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/h2-console/**");//配置防止h2-console下的页面无法正常访问,这个网址看内嵌的h2数据库。
}
//密码加密的方法
@Bean
PasswordEncoder passwordEncoder() {
val idForDefault = "bcrypt";
val encoders = Map.of(
idForDefault, new BCryptPasswordEncoder(),
"SHA-1", new MessageDigestPasswordEncoder("SHA-1")
);
return new DelegatingPasswordEncoder(idForDefault, encoders);
}
}