第四章 认证
使用数据库保存/查询用户数据,完成认证功能
4.1 方式一:重写jdbcAuthentication规则(不推荐)
- 基于数据库的RBAC查询出我们需要的用户以及这些用户的权限(权限标识、角色)
- 创建和SpringSecurity要求一模一样的表,然后用默认jdbcAuthentication
- 更新jdbcAuthentication里面所有我们需要实际运行的sql
- authoritiesByUsernameQuery:根据用户名查询他权限的sql
- usersByUsernameQuery:根据用户名查询用户的sql
- .......:更多的sql均可定义
4.1.1 使用默认的查询用户语句
auth.jdbcAuthentication().usersByUsernameQuery("zhangsan"); |
4.1.2 使用默认的查询权限语句
auth.jdbcAuthentication().authoritiesByUsernameQuery("zhangsan"); |
4.2 方式二:自定义UserDetailsService检索用户
4.2.1 实现UserDetailService接口loadUserByUsername(String username)方法
4.2.2 实验步骤
1 创建表结构
security实验\security.sql
2 配置 configure(AuthenticationManagerBuilder auth)
@Autowired UserDetailsService userDetailsService;//用户详情查询服务组件的接口
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //根据用户名查询出用户的详细信息 auth.userDetailsService(userDetailsService); } |
3 编写UserDetailService实现:
- 接口及已有实现类
- 实现UserDetailService接口,提供自定义实现类
org.springframework.security.core.userdetails.UserDetailsService
package com.atguigu.security.component;
import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.core.authority.*; import org.springframework.security.core.userdetails.User; import org.springframework.stereotype.Service;
//按照用户名查询用户详情的接口 @Service public class AppUserDetailsServiceImpl implements UserDetailsService { @Autowired JdbcTemplate jdbcTemplate;
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { String queryUser = "SELECT * FROM `t_admin` WHERE loginacct=?";
//1、查询指定用户的信息 Map<String, Object> map = jdbcTemplate.queryForMap(queryUser, username);
//2、将查询到的用户封装到框架使用的UserDetails里面 return new User(map.get("loginacct").toString(), map.get("userpswd").toString(), AuthorityUtils.createAuthorityList("ADMIN","USER"));//暂时写死,过后数据库中查 } } |
4 运行测试结果,密码不一致,跳转到登录页,并提示错误消息
4.2.3 debug测试登录-断点调试
1 断点-方法栈
2 自定义UserDetailService实现类
3 Dao层认证提供者: DaoAuthenticationProvider
Dao层认证提供者DaoAuthenticationProvider,用于调用自定义的UserDetailService实现类方法
4 抽象层用户认证提供者: AbstractUserDetailsAuthenticationProvider
抽象层用户认证提供者,获取dao层查找的认证用户信息,被封装成UserDetails对象,User类是UserDetails接口实现类
1)org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks认证用户账号是否被锁定,是否启用,是否过期;用户表中可以增加这些字段。
2)public interface Authentication extends Principal 封装表单提交的认证信息
认证用户名和密码;盐值为null
采用org.springframework.security.authentication.encoding.BasePasswordEncoder默认加密器对表单提交明文加密(其实并没有进行任何加密,明文无变化)
- 总结
4.3 基于数据库(MD5密码)认证 (debug)
使用数据库保存/查询用户数据,完成认证功能
4.3.1 配置 configure(AuthenticationManagerBuilder auth)
org.springframework.security.crypto.password.PasswordEncoder
//测试:分析源码(验证密码不一致) auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); |
4.3.2 引入MD5加密工具类:MD5Util.java
4.3.3 PasswordEncoder接口实现类:AppPasswordEncoder
@Service public class AppPasswordEncoder implements PasswordEncoder {
/** * 密码加密的算法 */ @Override public String encode(CharSequence rawPassword) { String digestPwd = MD5Util.digestPwd(rawPassword.toString()); return digestPwd; }
/** * 比较登录密码和数据库存储密码是否一致 * rawPassword:页面的明文密码 * encodedPassword:数据库的密文密码 */ @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { //使用自己的工具类 String digestPwd = MD5Util.digestPwd(rawPassword.toString()); return digestPwd.equals(encodedPassword); } } |
4.3.4 Debug测试,主要测试matches方法的调用过程
- 表单提交密码:rawPassword
- 数据库存储密码 :encodePassword
- 调用自定义密码验证器
- 密码不一致,抛异常:Bad credentials ;密码一致,通过认证
- 创建UsernamePasswordAuthenticationToken 对象,封装认证信息
4.3.5 源码参考
protected Authentication createSuccessAuthentication(Object principal,Authentication authentication, UserDetails user) { // Ensure we return the original credentials the user supplied, // so subsequent attempts are successful even with encoded passwords. // Also ensure we return the original getDetails(), so that future // authentication events after cache expiry contain the details UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken( principal, authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities()) );//封装用户权限信息 result.setDetails(authentication.getDetails()); //封装用户信息 return result; } |
1 principal 认证主体-数据库中查询User数据
2 authentication.getCredentials() 认证密码(表单中密码)
3 authoritiesMapper.mapAuthorities(user.getAuthorities()) 认证权限集合
该用户拥有的权限,暂时写死在代码中的,后期要根据用户查询所拥有的权限
4 认证细节:包括客户端ip和sessionid
org.springframework.security.web.authentication.WebAuthenticationDetails
5 result对象(UsernamePasswordAuthenticationToken)详细描述
4.4 基于数据库(BCryptPasswordEncoder)密码加密认证
4.4.1 PasswordEncoder接口
4.4.2 使用BCryptPasswordEncoder进行密码加密
//推荐密码加密器用这个BCryptPasswordEncoder; 将一个字符串加密成一个永不重复的密文 //1、加盐+加随机数 auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder()); |
4.4.3 本地测试:main方法
public static void main(String[] args) { BCryptPasswordEncoder pe = new BCryptPasswordEncoder();
//$2a$10$WzKk37ncOPynOSxyFGkxWu3ys7xaf7L/9uUhfVYVOCFTqeHkgJvOq //$2a$10$VmWwIx/uxNQabCYl3I5mZ.U9sQvpiM/xAhX69Skg0EWyDm3twQfcO //$2a$10$2Ig1mxqlb033XcU7aB0Ck.OZouRLsHUkJyIl9Mzi40FIY6grcEUr6 //大致的规律:$2a$10$+"xxx"+"/"+"xxx" String encode = pe.encode("123456"); System.out.println(encode); } |
4.4.4 服务器运行测试
将main方法生成的密文存储到数据库中(注意:userpswd字段长度),重新启动服务器进行测试。