在元婴篇,学习了Spring Security,实现了不连接数据库进行授权验证,下面是Spring Security+JDBC查询数据库的实践
持久层实现类—findByLoginName方法
@Repository
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public User findByLoginName(String username) {
String sql = "select * from user where username = '"+username+"'";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class));
}
}
定义一个UserService类,实现UserDetailsService
注意点:返回的权限必须是“ROLE_”开头,比如权限是vip1,我返回告诉spring security的时候,必须告诉他权限是ROLE_vip1,这样spring security才会认为权限是vip1。
@Service
public class UserService implements UserDetailsService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("进入service,用户名为 : " + username);
if(username == null || username.equals("")){
throw new UsernameNotFoundException("请输入用户名!");
}
List<SimpleGrantedAuthority> list = new ArrayList<>();
User user = userDao.findByLoginName(username);
for(String s : user.getRole().split(" ")){
s = "ROLE_" + s;
//由于不可能是空的(数据库中必须字段)
list.add(new SimpleGrantedAuthority(s));
System.out.println(s);
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), list);
}
}
对Spring Security的架构和核心类进行高度的概括,他们分别是一个或者两个核心接口以及其实现类, Spring Security中进行身份验证的是AuthenticationManager接口,ProviderManager是它的一个默认实现,但它并不用来处理身份认证,而是委托给配置好的AuthenticationProvider列表,每个AuthenticationProvider会轮流检查身份认证。检查后或者返回Authentication对象或者抛出异常。
大多数的身份认证提供程序都利用了UserDetails和UserDetailsService接口,身份认证最常用的方法是加载相应的UserDetails并检查加载的密码和用户输入的密码,UserService接口里面只有一个用来给用户获取信息的loadUserByUsername
方法,返回的就是一个UserDetails对象,AuthenticationManager
会加载这个对象进行检查。
SecurityConfig类
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
//链式编程
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能页只有有对应权限的人才能访问
//请求授权的规则
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//没有权限默认到登录页,需要开启登录的页面
//usernameParameter和passwordParameter的参数固定为username和password
http.formLogin().usernameParameter("username").passwordParameter("password");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(new CustomPasswordEncoder());
}
}
CustomPasswordEncoder
CustomPasswordEncoder
是自定义的继承自PasswordEncoder
的密码编码类
public class CustomPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString();
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(rawPassword);
}
}
正常加密与匹配的流程如下:
(1)加密(encode):注册用户时,使用SHA-256+随机盐+密钥把用户输入的密码进行hash处理,得到密码的hash值,然后将其存入数据库中。
(2)用户登录时,密码匹配阶段并没有进行密码解密(因为密码经过Hash处理,是不可逆的),而是使用相同的算法把用户输入的密码进行hash处理,得到密码的hash值,然后将其与从数据库中查询到的密码hash值进行比较。
因为数据库中的数据是事先存入的,没有进行加密,所以使用自定义类对密码编码的方法重写,通过直接比较内容来判断密码是否正确。