Spring Security 持久化

Mybatis Plus Version

参考 osvue.githuo.io

Spring Data Jpa Version

创建 Spring Boot 项目

 <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>

配置用户实体类

@Entity(name = "t_user")
public class User implements UserDetails {
    @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;
    }
    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

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

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

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

    @Override
    public boolean isEnabled() {
        return enabled;
    }
    //省略其他 get/set 方法
}
  • 用户实体类主要需要实现 UserDetails 接口,并实现接口中的方法。
  1. accountNonExpired、accountNonLocked、credentialsNonExpired、enabled 这四个属性分别用来描述用户的状态,表示账户是否没有过期、账户是否没有被锁定、密码是否没有过期、以及账户是否可用。
  2. roles 属性表示用户的角色,User 和 Role 是多对多关系,用一个 @ManyToMany 注解来描述。
  3. getAuthorities 方法返回用户的角色信息,我们在这个方法中把自己的 Role 稍微转化一下即可。

角色实体类

@Entity(name = "t_role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String nameZh;
    //省略 getter/setter
}

UserDao 省略 , 详情参考JAP ...

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 Config


/**
 * @Author: Mr.Han
 * @Description: spring_boot_plus
 * @Date: Created in 2020/4/17_14:07
 * @Modified By: THE GIFTED
 */

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  @Autowired
  UserService userService;
  /*
    // TODO: 2020/4/17  原来在内存中存取用户
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
        .withUser("os")
        .password(new BCryptPasswordEncoder().encode("123"))
        .roles("admin")
        .and()
        .withUser("vue")
        .password(new BCryptPasswordEncoder().encode("123"))
        .roles("user");
  }*/

  /**
   * @Function TODO 现在在数据库中存取
   *
   * @author THE GIFTED @Date 2020/4/17 14:24
   * @param
   * @return
   */
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userService);
  }

  @Bean
  PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }



  @Override
  public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/js/**", "/css/**", "/images/**");
  }

 /**
  * @Function 配置登录规则
  * @author   THE GIFTED
  * @Date     2020/4/17 14:37
  * @param
  * @return
  */
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/admin/**").hasRole("admin")
        .antMatchers("/user/**").hasRole("user")
        .anyRequest().authenticated()
        .and()
        .formLogin()
        .loginProcessingUrl("/doLogin")
        //登录成功的回调
        .successHandler((req, resp, authentication) -> {
          Object principal = authentication.getPrincipal();
          resp.setContentType("application/json;charset=utf-8");
          PrintWriter out = resp.getWriter();
          out.write(new ObjectMapper().writeValueAsString(principal));
          out.flush();
          out.close();
        })
        .failureHandler((req, resp, e) -> {
          //登录失败回调
          resp.setContentType("application/json;charset=utf-8");
          PrintWriter out = resp.getWriter();
          out.write(e.getMessage());
          out.flush();
          out.close();
        })
        .permitAll()
        .and()
        .logout()
        .logoutUrl("/logout")
        .logoutSuccessHandler((req, resp, authentication) -> {
          resp.setContentType("application/json;charset=utf-8");
          PrintWriter out = resp.getWriter();
          out.write("注销成功");
          out.flush();
          out.close();
        })
        .permitAll()
        .and()
        .csrf().disable().exceptionHandling()
        .authenticationEntryPoint((req, resp, authException) -> {
          //未认证回调
              resp.setContentType("application/json;charset=utf-8");
              PrintWriter out = resp.getWriter();
              out.write("尚未登录,请先登录");
              out.flush();
              out.close();
            }
        );
  }
}

application.properties

spring.datasource.username=root
spring.datasource.password=0
spring.datasource.url=jdbc:mysql:///ssm?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

测试

{
    "id": 4,
    "username": "vue",
    "password": "$2a$10$GAouRFRbarO7ANR3N4aVCetKsIl1cMnDPKoon7wHRptA6przxhrKC",
    "accountNonExpired": true,
    "accountNonLocked": true,
    "credentialsNonExpired": true,
    "enabled": true,
    "roles": [
        {
            "id": 4,
            "name": "ROLE_user",
            "nameZh": "普通用户"
        }
    ],
    "authorities": [
        {
            "authority": "ROLE_user"
        }
    ]
}