Spring Boot + Spring Security + MySQL的 JPA 身份验证

x33g5p2x  于2021-10-15 转载在 Spring  
字(9.1k)|赞(0)|评价(0)|浏览(394)

本文将重点介绍使用 Spring Boot 的 Spring Security 与 JPA 和 MySQL 数据库的身份验证过程。通过 Spring Data JPA 对数据库中的用户信息进行身份验证是一个简单的过程。

正如之前通过 JDBC 进行 Spring Security 身份验证中所分享的那样,希望您对 Spring Security 有一些基本的了解。

让我们来看看 Spring Security 是如何管理认证的:

你将构建什么

您将构建 3 个 REST 服务,该服务将在以下位置接受 HTTP GET 请求:

1- http://localhost:8080/admin
2- http://localhost:8080/user
3- http://localhost:8080/all

并分别以问候的 HTML 表示形式响应:

1- Welcome Admin!
2- Welcome User!
3- Hello Everyone!

但在访问上述服务之前,用户必须使用存储在 MySQL 数据库中的凭据进行身份验证:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {		
	auth.userDetailsService(userDetailsService);
}

并从数据库中获取用户信息,覆盖 Spring Security 本身提供的 UserDetailsService 接口的 loadUserByUsername(String *userName*) 方法。

@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {

	Optional<User> user = userRepository.findByUserName(userName);

	user.orElseThrow(() -> new UsernameNotFoundException(userName + " not found."));

	return user.map(UserDetailsImpl::new).get();
}

认证成功后,用户必须具有有效的权限/角色才能访问服务:

@Override
protected void configure(HttpSecurity http) throws Exception {
	http.authorizeRequests()
		.antMatchers("/admin").hasRole(ADMIN)
		.antMatchers("/user").hasAnyRole(ADMIN,USER)
		.antMatchers("/all").permitAll()
		.and().formLogin();
}

###你需要什么

  • 约 30 分钟

  • 最喜欢的文本编辑器或 IDE

  • JDK 1.8 或更高版本

  • Gradle 4+ 或 Maven 3.2+

  • MySQL 数据库

  • 您也可以将代码直接导入您的 IDE:

  • 弹簧工具套件 (STS)

  • 日食

  • IntelliJ IDEA

需要依赖

要使用 Spring Data JPA 和 Spring Security,请将以下依赖项添加到 pom.xml
pom.xml

<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>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<scope>runtime</scope>
	</dependency>
</dependencies>

项目结构

我们在 STS 4 IDE 中的应用程序的最终项目结构如下所示:

控制器

MyController 类为应用程序用户公开 REST 端点。在这个控制器类中,我们创建了 3 个不同的 REST 端点,如下所示:

  1. /admin 只能由具有角色 ‘ADMIN’ 的用户访问。
  2. /user 允许角色为 'ADMIN''USER' 的用户。
  3. /all 允许所有人使用。

我的控制器

package org.websparrow.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {

	@GetMapping("/admin")
	public String admin() {
		return "<h2>Welcome Admin!</h2>";
	}

	@GetMapping("/user")
	public String user() {
		return "<h2>Welcome User!</h2>";
	}

	@GetMapping("/all")
	public String all() {
		return "<h2>Hello Everyone!</h2>";
	}
}

实体

User 类是一个实体类,代表user 表结构并保存所有必要的用户信息。
用户.java

package org.websparrow.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "user")
public class User {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Integer id;
	private String userName;
	private String password;
	private boolean isActive;
	private String roles;

	// Generate Getters and Setters...
}

存储库

UserRepository 接口用于查询数据库和获取用户信息。为此,您必须创建一个派生的 findBy 查询方法 findByUserName(String *userName*)
UserRepository.java

package org.websparrow.repository;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.websparrow.entity.User;

@Repository
public interface UserRepository extends JpaRepository<User, Integer> {

	Optional<User> findByUserName(String userName);
}

服务

UserDetailsServiceImpl 是 Spring Framework 提供的 UserDetailsService 接口的实现类。您必须覆盖其 loadUserByUsername(String *userName*) 方法,该方法返回 UserDetails 接口的实例。
UserDetailsS​​erviceImpl.java

package org.websparrow.service;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
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 org.websparrow.entity.User;
import org.websparrow.repository.UserRepository;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

	@Autowired
	private UserRepository userRepository;

	@Override
	public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {

		Optional<User> user = userRepository.findByUserName(userName);

		user.orElseThrow(() -> new UsernameNotFoundException(userName + " not found."));

		return user.map(UserDetailsImpl::new).get();
	}

}

UserDetailsImpl 类实现了 UserDetails 接口来保存所有用户信息。

UserDetailsImpl.java

package org.websparrow.service;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.websparrow.entity.User;

public class UserDetailsImpl implements UserDetails {

	private static final long serialVersionUID = 1L;

	private String userName;
	private String password;
	private boolean isActive;
	private List<GrantedAuthority> authorities;

	public UserDetailsImpl(User user) {
		this.userName = user.getUserName();
		this.password = user.getPassword();
		this.isActive = user.isActive();
		this.authorities = Arrays.stream(user.getRoles().split(","))
				.map(SimpleGrantedAuthority::new)
				.collect(Collectors.toList());
	}

	public UserDetailsImpl() {}

	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		return authorities;
	}

	@Override
	public String getPassword() {
		return password;
	}

	@Override
	public String getUsername() {

		return userName;
	}

	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	@Override
	public boolean isEnabled() {
		return isActive;
	}

}

Spring Security Java 配置

创建一个扩展 WebSecurityConfigurerAdapter 的 Spring Security 配置类。通过添加 @EnableWebSecurity,您可以获得 Spring Security 支持。覆盖其 configure(AuthenticationManagerBuilder *auth*) 方法并调用 [ [$19$]] 类的 userDetailsService() 方法,传递 UserDetailsService 对象,其余部分将由 Spring Security 自动管理。
SecurityConfiguration.java

package org.websparrow.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

	private static final String ADMIN = "ADMIN";
	private static final String USER = "USER";

	@Autowired
	private UserDetailsService userDetailsService;

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(userDetailsService);
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests().antMatchers("/admin").hasRole(ADMIN)
			.antMatchers("/user").hasAnyRole(ADMIN, USER)
			.antMatchers("/all").permitAll()
			.and().formLogin();
	}

	@Bean
	public PasswordEncoder getPasswordEncoder() {
		return NoOpPasswordEncoder.getInstance();
	}
}

记住
1.不要在生产中使用NoOpPasswordEncoder来编码密码。我们仅用于演示目的。

  1. Spring Security 足够智能,可以管理大部分事情,您不必担心。

3.你可以随时更改身份验证,无论你是使用JDBC、文本文件、硬编码值等。这就是Spring Security框架的美妙之处。

application.properties

application.properties 文件中配置数据库连接字符串,以建立应用程序和数据库之间的连接。
application.properties

# MySQL database connection strings
spring.datasource.url=jdbc:mysql://localhost:3306/demo
spring.datasource.username=root
spring.datasource.password=root

# JPA property settings
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.show_sql=true

运行应用程序

BootSecurityJpaApp 类包含主要方法并负责启动应用程序。
BootSecurityJpaApp.java

package org.websparrow;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BootSecurityJpaApp {

	public static void main(String[] args) {
		SpringApplication.run(BootSecurityJpaApp.class, args);
	}
}

测试应用程序

要测试应用程序,请通过执行上述类来启动 Spring Boot 应用程序,并按照以下步骤操作:

加载用户信息:

  1. 通过执行以下 SQL 脚本,2 个虚拟用户将被插入到您的数据库中:
insert  into `user`(`id`,`is_active`,`password`,`roles`,`user_name`)
values (1,1,'user@123','ROLE_USER','user'),
(2,1,'admin@123','ROLE_ADMIN','admin');

对于 /admin 页面:

  1. 点击 localhost:8080/admin,它会将您重定向到登录页面。
  2. 以具有“ADMIN”角色的用户登录,验证成功后,将显示管理页面。
  3. 同样,尝试使用没有“ADMIN”角色的用户访问管理 URL(用户具有“USER”角色),Spring Security 将阻止您访问 /admin 页面。

对于/用户页面:

  1. 点击 localhost:8080/user,它会将您重定向到登录页面。
  2. 以“USER”用户身份登录,认证成功后显示用户页面。
  3. 用户拥有角色“ADMIN”也可以访问。

对于/所有页面:

  1. Spring Security 允许所有人访问 localhost:8080/all URL。它不需要进行身份验证。

总结

恭喜!您刚刚使用 Spring Boot 使用 MySQL 数据库开发了 Spring Security JPA 身份验证。

相关文章