使用 JPA 及 UserDetailsService

本文展示了如何使用 JPA 自定义 UserDetailsService 及数据库 的方式来进行认证。在本例,我们将认证信息存储于 H2 数据库中。

ldap-authentication项目的基础上,我们构建了一个jpa-userdetailsservice项目。

build.gradle

修改 build.gradle 文件,让我们的jpa-userdetailsservice项目成为一个新的项目。

修改内容也比较简单,修改项目名称及版本即可。

  1. jar {
  2. baseName = 'jpa-userdetailsservice'
  3. version = '1.0.0'
  4. }

我们将 spring-boot-starter-jdbc依赖改为了 spring-boot-starter-data-jpa

实体

修改 User

修改 User,增加了 username、password 两个字段,用于登录时使用。authorities 字段,用于存储该用户的权限信息。同时,User 也实现了 org.springframework.security.core.userdetails.UserDetails 接口。其中 User 与 Authority是多对多的关系。

  1. @Entity // 实体
  2. public class User implements UserDetails, Serializable {
  3. private static final long serialVersionUID = 1L;
  4. @Id // 主键
  5. @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略
  6. private Long id; // 用户的唯一标识
  7. @Column(nullable = false, length = 50) // 映射为字段,值不能为空
  8. private String name;
  9. @Column(nullable = false)
  10. private Integer age;
  11. @Column(nullable = false, length = 50, unique = true)
  12. private String username; // 用户账号,用户登录时的唯一标识
  13. @Column(length = 100)
  14. private String password; // 登录时密码
  15. @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
  16. @JoinTable(name = "user_authority", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
  17. inverseJoinColumns = @JoinColumn(name = "authority_id", referencedColumnName = "id"))
  18. private List<Authority> authorities;
  19. protected User() { // JPA 的规范要求无参构造函数;设为 protected 防止直接使用
  20. }
  21. public User(String name, Integer age) {
  22. this.name = name;
  23. this.age = age;
  24. }
  25. public Long getId() {
  26. return id;
  27. }
  28. public void setId(Long id) {
  29. this.id = id;
  30. }
  31. public String getName() {
  32. return name;
  33. }
  34. public void setName(String name) {
  35. this.name = name;
  36. }
  37. public Integer getAge() {
  38. return age;
  39. }
  40. public void setAge(Integer age) {
  41. this.age = age;
  42. }
  43. @Override
  44. public Collection<? extends GrantedAuthority> getAuthorities() {
  45. return this.authorities;
  46. }
  47. public void setAuthorities(List<Authority> authorities) {
  48. this.authorities = authorities;
  49. }
  50. @Override
  51. public String getUsername() {
  52. return username;
  53. }
  54. public void setUsername(String username) {
  55. this.username = username;
  56. }
  57. @Override
  58. public String getPassword() {
  59. return password;
  60. }
  61. public void setPassword(String password) {
  62. this.password = password;
  63. }
  64. @Override
  65. public boolean isAccountNonExpired() {
  66. return true;
  67. }
  68. @Override
  69. public boolean isAccountNonLocked() {
  70. return true;
  71. }
  72. @Override
  73. public boolean isCredentialsNonExpired() {
  74. return true;
  75. }
  76. @Override
  77. public boolean isEnabled() {
  78. return true;
  79. }
  80. @Override
  81. public String toString() {
  82. return String.format("User[id=%d, username='%s', name='%s', age='%d', password='%s']", id, username, name, age,
  83. password);
  84. }
  85. }

新增 Authority

新增 Authority 用于存储权限信息。 Authority 实现了 GrantedAuthority 接口,并重写了其 getAuthority 方法。

  1. @Entity // 实体
  2. public class Authority implements GrantedAuthority {
  3. private static final long serialVersionUID = 1L;
  4. @Id // 主键
  5. @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略
  6. private Long id; // 用户的唯一标识
  7. @Column(nullable = false) // 映射为字段,值不能为空
  8. private String name;
  9. public Long getId() {
  10. return id;
  11. }
  12. public void setId(Long id) {
  13. this.id = id;
  14. }
  15. /*
  16. * (non-Javadoc)
  17. *
  18. * @see org.springframework.security.core.GrantedAuthority#getAuthority()
  19. */
  20. @Override
  21. public String getAuthority() {
  22. return name;
  23. }
  24. public void setName(String name) {
  25. this.name = name;
  26. }
  27. }

新增存储库

新增com.waylau.spring.boot.security.repository.UserRepository,由于继承自了 org.springframework.data.jpa.repository.JpaRepository接口,所以大部分与数据库操作的接口,我们在UserRepository都无需定义了。我们只需要定义一个

  1. public interface UserRepository extends JpaRepository<User, Long>{
  2. User findByUsername(String username);
  3. }

findByUsername是用来根据用户账号来查询用户,这个在查询用户权限时要用到。

服务类

我们的服务类继承自我们定义的UserService,以及org.springframework.security.core.userdetails.UserDetailsService。其中,UserDetailsService是用来查询认证信息相关的接口,我们重写其loadUserByUsername方法。

  1. @Service
  2. public class UserServiceImpl implements UserService,UserDetailsService {
  3. @Autowired
  4. private UserRepository userRepository;
  5. ......
  6. @Override
  7. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  8. return userRepository.findByUsername(username);
  9. }
  10. }

修改配置类 SecurityConfig

在配置类中,我们把 UserDetailsService 的实现赋给 AuthenticationManagerBuilder:

  1. @Autowired
  2. private UserDetailsService userDetailsService;
  3. ......
  4. /**
  5. * 认证信息管理
  6. * @param auth
  7. * @throws Exception
  8. */
  9. @Autowired
  10. public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
  11. auth.userDetailsService(userDetailsService);
  12. }

初始化数据库

用于是使用了 JPA ,所以,默认表结构是会根据实体逆向生成的。

至于数据,我们在项目的 src/main/resources 目录下,放置一个 import.sql 脚本文件。里面是我们要初始化的数据,只要应用启动,就会自动把 import.sql 的数据给导入数据库。

以下为我们要初始化的数据:

  1. INSERT INTO user (id, username, password, name, age) VALUES (1, 'waylau', '123456', '老卫', 30);
  2. INSERT INTO user (id, username, password, name, age) VALUES (2, 'admin', '123456', 'Way Lau', 29);
  3. INSERT INTO authority (id, name) VALUES (1, 'ROLE_USER');
  4. INSERT INTO authority (id, name) VALUES (2, 'ROLE_ADMIN');
  5. INSERT INTO user_authority (user_id, authority_id) VALUES (1, 1);
  6. INSERT INTO user_authority (user_id, authority_id) VALUES (2, 1);
  7. INSERT INTO user_authority (user_id, authority_id) VALUES (2, 2);