使用 JPA 及 UserDetailsService
本文展示了如何使用 JPA 自定义 UserDetailsService 及数据库 的方式来进行认证。在本例,我们将认证信息存储于 H2 数据库中。
在 ldap-authentication项目的基础上,我们构建了一个jpa-userdetailsservice项目。
build.gradle
修改 build.gradle 文件,让我们的jpa-userdetailsservice项目成为一个新的项目。
修改内容也比较简单,修改项目名称及版本即可。
jar {baseName = 'jpa-userdetailsservice'version = '1.0.0'}
我们将 spring-boot-starter-jdbc依赖改为了 spring-boot-starter-data-jpa
实体
修改 User
修改 User,增加了 username、password 两个字段,用于登录时使用。authorities 字段,用于存储该用户的权限信息。同时,User 也实现了 org.springframework.security.core.userdetails.UserDetails 接口。其中 User 与 Authority是多对多的关系。
@Entity // 实体public class User implements UserDetails, Serializable {private static final long serialVersionUID = 1L;@Id // 主键@GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略private Long id; // 用户的唯一标识@Column(nullable = false, length = 50) // 映射为字段,值不能为空private String name;@Column(nullable = false)private Integer age;@Column(nullable = false, length = 50, unique = true)private String username; // 用户账号,用户登录时的唯一标识@Column(length = 100)private String password; // 登录时密码@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)@JoinTable(name = "user_authority", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),inverseJoinColumns = @JoinColumn(name = "authority_id", referencedColumnName = "id"))private List<Authority> authorities;protected User() { // JPA 的规范要求无参构造函数;设为 protected 防止直接使用}public User(String name, Integer age) {this.name = name;this.age = age;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authorities;}public void setAuthorities(List<Authority> authorities) {this.authorities = authorities;}@Overridepublic String getUsername() {return username;}public void setUsername(String username) {this.username = username;}@Overridepublic String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}@Overridepublic String toString() {return String.format("User[id=%d, username='%s', name='%s', age='%d', password='%s']", id, username, name, age,password);}}
新增 Authority
新增 Authority 用于存储权限信息。 Authority 实现了 GrantedAuthority 接口,并重写了其 getAuthority 方法。
@Entity // 实体public class Authority implements GrantedAuthority {private static final long serialVersionUID = 1L;@Id // 主键@GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略private Long id; // 用户的唯一标识@Column(nullable = false) // 映射为字段,值不能为空private String name;public Long getId() {return id;}public void setId(Long id) {this.id = id;}/** (non-Javadoc)** @see org.springframework.security.core.GrantedAuthority#getAuthority()*/@Overridepublic String getAuthority() {return name;}public void setName(String name) {this.name = name;}}
新增存储库
新增com.waylau.spring.boot.security.repository.UserRepository,由于继承自了 org.springframework.data.jpa.repository.JpaRepository接口,所以大部分与数据库操作的接口,我们在UserRepository都无需定义了。我们只需要定义一个
public interface UserRepository extends JpaRepository<User, Long>{User findByUsername(String username);}
findByUsername是用来根据用户账号来查询用户,这个在查询用户权限时要用到。
服务类
我们的服务类继承自我们定义的UserService,以及org.springframework.security.core.userdetails.UserDetailsService。其中,UserDetailsService是用来查询认证信息相关的接口,我们重写其loadUserByUsername方法。
@Servicepublic class UserServiceImpl implements UserService,UserDetailsService {@Autowiredprivate UserRepository userRepository;......@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {return userRepository.findByUsername(username);}}
修改配置类 SecurityConfig
在配置类中,我们把 UserDetailsService 的实现赋给 AuthenticationManagerBuilder:
@Autowiredprivate UserDetailsService userDetailsService;....../*** 认证信息管理* @param auth* @throws Exception*/@Autowiredpublic void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService);}
初始化数据库
用于是使用了 JPA ,所以,默认表结构是会根据实体逆向生成的。
至于数据,我们在项目的 src/main/resources 目录下,放置一个 import.sql 脚本文件。里面是我们要初始化的数据,只要应用启动,就会自动把 import.sql 的数据给导入数据库。
以下为我们要初始化的数据:
INSERT INTO user (id, username, password, name, age) VALUES (1, 'waylau', '123456', '老卫', 30);INSERT INTO user (id, username, password, name, age) VALUES (2, 'admin', '123456', 'Way Lau', 29);INSERT INTO authority (id, name) VALUES (1, 'ROLE_USER');INSERT INTO authority (id, name) VALUES (2, 'ROLE_ADMIN');INSERT INTO user_authority (user_id, authority_id) VALUES (1, 1);INSERT INTO user_authority (user_id, authority_id) VALUES (2, 1);INSERT INTO user_authority (user_id, authority_id) VALUES (2, 2);
