在元婴篇,学习了Spring Security,实现了不连接数据库进行授权验证,下面是Spring Security+JDBC查询数据库的实践

持久层实现类—findByLoginName方法

  1. @Repository
  2. public class UserDaoImpl implements UserDao{
  3. @Autowired
  4. private JdbcTemplate jdbcTemplate;
  5. @Override
  6. public User findByLoginName(String username) {
  7. String sql = "select * from user where username = '"+username+"'";
  8. return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class));
  9. }
  10. }

定义一个UserService类,实现UserDetailsService

注意点:返回的权限必须是“ROLE_”开头,比如权限是vip1,我返回告诉spring security的时候,必须告诉他权限是ROLE_vip1,这样spring security才会认为权限是vip1。

  1. @Service
  2. public class UserService implements UserDetailsService {
  3. @Autowired
  4. private JdbcTemplate jdbcTemplate;
  5. @Autowired
  6. private UserDao userDao;
  7. @Override
  8. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  9. System.out.println("进入service,用户名为 : " + username);
  10. if(username == null || username.equals("")){
  11. throw new UsernameNotFoundException("请输入用户名!");
  12. }
  13. List<SimpleGrantedAuthority> list = new ArrayList<>();
  14. User user = userDao.findByLoginName(username);
  15. for(String s : user.getRole().split(" ")){
  16. s = "ROLE_" + s;
  17. //由于不可能是空的(数据库中必须字段)
  18. list.add(new SimpleGrantedAuthority(s));
  19. System.out.println(s);
  20. }
  21. return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), list);
  22. }
  23. }

对Spring Security的架构和核心类进行高度的概括,他们分别是一个或者两个核心接口以及其实现类, Spring Security中进行身份验证的是AuthenticationManager接口,ProviderManager是它的一个默认实现,但它并不用来处理身份认证,而是委托给配置好的AuthenticationProvider列表,每个AuthenticationProvider会轮流检查身份认证。检查后或者返回Authentication对象或者抛出异常。
大多数的身份认证提供程序都利用了UserDetails和UserDetailsService接口,身份认证最常用的方法是加载相应的UserDetails并检查加载的密码和用户输入的密码,UserService接口里面只有一个用来给用户获取信息的loadUserByUsername方法,返回的就是一个UserDetails对象,AuthenticationManager会加载这个对象进行检查。

SecurityConfig类

  1. @EnableWebSecurity
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Autowired
  4. private UserService userService;
  5. //链式编程
  6. //授权
  7. @Override
  8. protected void configure(HttpSecurity http) throws Exception {
  9. //首页所有人可以访问,功能页只有有对应权限的人才能访问
  10. //请求授权的规则
  11. http.authorizeRequests()
  12. .antMatchers("/").permitAll()
  13. .antMatchers("/level1/**").hasRole("vip1")
  14. .antMatchers("/level2/**").hasRole("vip2")
  15. .antMatchers("/level3/**").hasRole("vip3");
  16. //没有权限默认到登录页,需要开启登录的页面
  17. //usernameParameter和passwordParameter的参数固定为username和password
  18. http.formLogin().usernameParameter("username").passwordParameter("password");
  19. }
  20. @Override
  21. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  22. auth.userDetailsService(userService).passwordEncoder(new CustomPasswordEncoder());
  23. }
  24. }

CustomPasswordEncoder

CustomPasswordEncoder是自定义的继承自PasswordEncoder的密码编码类

  1. public class CustomPasswordEncoder implements PasswordEncoder {
  2. @Override
  3. public String encode(CharSequence rawPassword) {
  4. return rawPassword.toString();
  5. }
  6. @Override
  7. public boolean matches(CharSequence rawPassword, String encodedPassword) {
  8. return encodedPassword.equals(rawPassword);
  9. }
  10. }

正常加密与匹配的流程如下:
(1)加密(encode):注册用户时,使用SHA-256+随机盐+密钥把用户输入的密码进行hash处理,得到密码的hash值,然后将其存入数据库中。
(2)用户登录时,密码匹配阶段并没有进行密码解密(因为密码经过Hash处理,是不可逆的),而是使用相同的算法把用户输入的密码进行hash处理,得到密码的hash值,然后将其与从数据库中查询到的密码hash值进行比较。
因为数据库中的数据是事先存入的,没有进行加密,所以使用自定义类对密码编码的方法重写,通过直接比较内容来判断密码是否正确。