Spring Security:整合Spring Data Jpa

Spring Security+Spring Data Jpa 強強聯手,安全管理沒有簡單,只有更簡單!

創建工程

首先我們創建一個新的 Spring Boot 工程,添加如下依賴:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
</dependency>

注意,除了 Spring Security 依賴之外,我們還需要數據依賴,lombok和 Spring Data Jpa 依賴。

工程創建完成後,我們再在數據庫中創建一個空的庫,就叫做 javakf_test1,裏邊什麼都不用做,這樣我們的準備工作就算完成了。

準備模型

接下來我們創建兩個實體類,分別表示用戶角色了用戶類:

用戶角色:

@Data
@Entity(name = "t_role")
public class Role {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	private String name;
	private String nameZh;

}

這個實體類用來描述用戶角色信息,有角色 id、角色名稱(英文、中文),@Entity 表示這是一個實體類,項目啓動後,將會根據實體類的屬性在數據庫中自動創建一個角色表。

用戶實體類:

@Data
@Entity(name = "t_user")
public class User implements UserDetails {

	private static final long serialVersionUID = -5470916854745278810L;
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	private String username;
	private String password;
	private boolean accountNonExpired;
	private boolean accountNonLocked;
	private boolean credentialsNonExpired;
	private boolean enabled;
	@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
	private List<Role> roles;

	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		List<SimpleGrantedAuthority> authorities = new ArrayList<>();
		for (Role role : getRoles()) {
			authorities.add(new SimpleGrantedAuthority(role.getName()));
		}
		return authorities;
	}

}

用戶實體類主要需要實現 UserDetails 接口,並實現接口中的方法。

這裏的字段基本都好理解,幾個特殊的我來稍微說一下:

  1. accountNonExpired、accountNonLocked、credentialsNonExpired、enabled這四個屬性分別用來描述用戶的狀態,表示賬戶是否沒有過期、賬戶是否沒有被鎖定、密碼是否沒有過期、以及賬戶是否可用。
  2. roles 屬性表示用戶的角色,User 和 Role 是多對多關係,用一個 @ManyToMany 註解來描述。
  3. getAuthorities 方法返回用戶的角色信息,我們在這個方法中把自己的 Role 稍微轉化一下即可。

配置

數據模型準備好之後,我們再來定義一個 UserDao:

public interface UserDao extends JpaRepository<User, Long> {

	User findUserByUsername(String username);

}

在接下來定義 UserService ,如下:

@Service
public class UserService implements UserDetailsService {

	@Autowired
	UserDao userDao;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		User user = userDao.findUserByUsername(username);
		if (user == null) {
			throw new UsernameNotFoundException("用戶不存在");
		}
		return user;
	}

}

我們自己定義的 UserService 需要實現 UserDetailsService 接口,實現該接口,就要實現接口中的方法,也就是 loadUserByUsername ,這個方法的參數就是用戶在登錄的時候傳入的用戶名,根據用戶名去查詢用戶信息(查出來之後,系統會自動進行密碼比對)。

配置完成後,接下來我們在 Spring Security 中稍作配置,Spring Security 和測試用的 HelloController 我還是沿用之前文章中的(Spring Security:用戶數據存入數據庫),主要列出來需要修改的地方。

在 SecurityConfig 中,我們通過如下方式來配置用戶:

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

大家注意,還是重寫 configure 方法,只不過這次我們不是基於內存,也不是基於 JdbcUserDetailsManager,而是使用自定義的 UserService,就這樣配置就 OK 了。

最後,我們再在 application.properties 中配置一下數據庫和 JPA 的基本信息,如下:

spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/javakf_test1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai

spring.jpa.database=mysql
spring.jpa.database-platform=mysql
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect

測試

首先我們來添加兩條測試數據,在單元測試中添加如下方法:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class UserTest {

	@Autowired
	UserDao userDao;

	@Test
	public void createUser() {
		User u1 = new User();
		u1.setUsername("javakf");
		u1.setPassword("123");
		u1.setAccountNonExpired(true);
		u1.setAccountNonLocked(true);
		u1.setCredentialsNonExpired(true);
		u1.setEnabled(true);
		List<Role> rs1 = new ArrayList<>();
		Role r1 = new Role();
		r1.setName("ROLE_admin");
		r1.setNameZh("管理員");
		rs1.add(r1);
		u1.setRoles(rs1);
		userDao.save(u1);
		User u2 = new User();
		u2.setUsername("test");
		u2.setPassword("456");
		u2.setAccountNonExpired(true);
		u2.setAccountNonLocked(true);
		u2.setCredentialsNonExpired(true);
		u2.setEnabled(true);
		List<Role> rs2 = new ArrayList<>();
		Role r2 = new Role();
		r2.setName("ROLE_user");
		r2.setNameZh("普通用戶");
		rs2.add(r2);
		u2.setRoles(rs2);
		userDao.save(u2);
	}

}

運行該方法後,我們會發現數據庫中多了三張表:
在這裏插入圖片描述
這就是根據我們的實體類自動創建出來的。

我們來查看一下表中的數據。

用戶表:
在這裏插入圖片描述
角色表:
在這裏插入圖片描述
用戶和角色關聯表:
在這裏插入圖片描述
有了數據,接下來啓動項目,我們來進行測試。

我們首先以 test 的身份進行登錄:
在這裏插入圖片描述
登錄成功後,分別訪問 /hello/admin/hello 以及 /user/hello 三個接口,其中:

  1. /hello 因爲登錄後就可以訪問,這個接口訪問成功。
  2. /admin/hello 需要 admin 身份,所以訪問失敗。
  3. /user/hello 需要 user 身份,所以訪問成功。

具體測試效果小夥伴們可以試試,我就不截圖了。

在測試的過程中,如果在數據庫中將用戶的 enabled 屬性設置爲 false,表示禁用該賬戶,此時再使用該賬戶登錄就會登錄失敗。

按照相同的方式,大家也可以測試 javakf 用戶。

代碼託管:springsecurity_example_6

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章