阅读全文,约 16 分钟
这是江帅帅的第022篇原创
Spring Security 的持久化案例
当我们在企业中处理登录认证和授权的时候,需要把信息存储到数据库中,我们的用户名和密码通过验证,然后认证和授权,最重要的还需要做加密处理。
结合 JPA 的案例
1)编辑 pom.xml 文件
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.nx</groupId> <artifactId>springbootdata</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!-- 添加spring-boot-starter-web模块依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 添加spring-boot-starter-thymeleaf模块依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- 添加spring-boot-starter-security 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- 添加mysql数据库驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- 添加spring-boot-starter-data-jpa模块 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
2)编辑 application.properties 文件
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true spring.datasource.username=root spring.datasource.password=1234 logging.level.org.springframework.security=trace spring.thymeleaf.cache=false spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true
3)创建 User 和 Role 持久化类
先看 Role 类。
@Entity @Table(name="tb_role") public class Role implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name="id") private Long id; @Column(name="authority") private String authority; public Role() { super(); } }
再看 User 类。
@Entity @Table(name="tb_user") public class User implements Serializable{ private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name="id") private Long id; private String loginName; private String username; private String password; @ManyToMany(cascade = {CascadeType.REFRESH},fetch = FetchType.EAGER) @JoinTable(name="tb_user_role", joinColumns={@JoinColumn(name="user_id")}, inverseJoinColumns={@JoinColumn(name="role_id")}) private List<Role> roles; // setXxx 和 getXxx 方法 }
4)创建 UserRepository 数据访问接口
public interface UserRepository extends JpaRepository<User, Long>{ // 根据登录名查询出用户 User findByLoginName(String loginName); }
5)创建 UserService 服务类
// 我们在这里需要实现 UserDetailsService 接口 // 因为配置的相关参数需要是 UserDetailsService 类型的数据 @Service public class UserService implements UserDetailsService{ // 注入持久层接口 UserRepository @Autowired UserRepository userRepository; // 重写 UserDetailsService 接口中的 loadUserByUsername 方法,通过该方法查询到对应的用户 // 其中定义了一些可以获取用户名、密码、权限等与认证相关的信息的方法。 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User User = userRepository.findByLoginName(username); if (User == null) { throw new UsernameNotFoundException("用户名不存在"); } // GrantedAuthority 对象代表赋予给当前用户的权限 List<GrantedAuthority> authorities = new ArrayList<>(); // 获得当前用户权限集合 List<Role> roles = User.getRoles(); for (Role role : roles) { // 将关联对象 Role 的 authority 属性保存为用户的认证权限 authorities.add(new SimpleGrantedAuthority(role.getAuthority())); } return new User(User.getUsername(), User.getPassword(), authorities); } }
6)创建 AppSecurityConfigurer 认证处理类
// 需要继承自WebSecurityConfigurerAdapter来完成,相关配置重写对应方法即可。 @Configuration public class AppSecurityConfigurer extends WebSecurityConfigurerAdapter{ // 注入用户服务类 @Autowired private UserService userService; // 注入加密接口 @Autowired private PasswordEncoder passwordEncoder; // 注入用户认证接口 @Autowired private AuthenticationProvider authenticationProvider; // 注入认证处理成功类,验证用户成功后处理不同用户跳转到不同的页面 @Autowired AppAuthenticationSuccessHandler appAuthenticationSuccessHandler; // BCryptPasswordEncoder 用来创建密码的加密程序,避免明文存储密码到数据库 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationProvider authenticationProvider() { // 创建DaoAuthenticationProvider对象 DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); // 不要隐藏"用户未找到"的异常 provider.setHideUserNotFoundExceptions(false); // 通过重写configure方法添加自定义的认证方式。 provider.setUserDetailsService(userService); // 设置密码加密程序认证 provider.setPasswordEncoder(passwordEncoder); return provider; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 设置认证方式。 auth.authenticationProvider(authenticationProvider); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/login","/css/**","/js/**","/img/*").permitAll() .antMatchers("/", "/home").hasRole("USER") .antMatchers("/admin/**").hasAnyRole("ADMIN", "DBA") .anyRequest().authenticated() .and() .formLogin().loginPage("/login").successHandler(appAuthenticationSuccessHandler) .usernameParameter("loginName").passwordParameter("password") .and() .logout().permitAll() .and() .exceptionHandling().accessDeniedPage("/accessDenied"); } }
上面主要是展示代码,呈现的 HTML 页面和数据,大家可以自行添加。