认证与授权
- 认证
解决我是谁
- 授权
解决我能做什么
1.1 原理
1.2 常见的过滤器
BasicAuthenticationFilter
- 如果在请求中找到一个Basic Auth Http 头,如果找到,则尝试用该头中的用户名和密码验证用户。
UsernamePasswordAuthenticationFilter
- 如果在请求参数或者post的Request Body 中找到用户名和密码,则尝试用这些值对用户进行身份验证。
DefaultLoginPageGeneratingFlter
- 默认登录页面生成过滤器,用于生成一个登录页面,如果你没有明确地禁用这个功能,那么就会生成一个登录页面,这就是为什么在启用Spring Security 时,会得到一个默认登录页面的原因
DefaultLoginOutPageGeneratingFilter
- 没有禁用该功能,则会生成一个注销页面
FilterSecurityInterceptor
- 过滤安全拦截器。用于授权逻辑
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 设置配置 :用于配置路径的安全性
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(req-> req.antMatchers("/test/**").authenticated())
.formLogin(AbstractHttpConfigurer::disable)
.httpBasic(withDefaults())
.csrf(AbstractHttpConfigurer::disable);
}
// 设置不拦截资源 常用于忽略某些资源
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().mvcMatchers("/public/**");
}
}
1.3 Remember-me功能
为解决session过期用户的直接访问问题。
原理:使用Cookie 存储用户名,过期时间,以及一个Hash
Hash:md5(用户名+过期时间+密码+key)
1.4 定制登录/退出登录的处理
- 登录成功后的处理:AuthenticationSuccessHandle
- 登录失败后的处理:AuthenticationFailureHandle
- 退出登录成功后的处理 :LogoutSuccessHandler
将json转化为java对象
ObjectMapper objectMapper = new ObjectMapper();
String carJson =
"{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";
Car car = objectMapper.readValue(carJson, Car.class);
将java对象转化为json
ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car();
String json = objectMapper.writeValueAsString(car);
1.5 自定义拦截器
继承需要定义的拦截器对其进行自定义配置
2 密码
明文-哈希-加盐-自适应-?
自适应哈希:
配置迭代次数 md5(md5(“XX”))
配置随机的盐值
迭代次数和盐值存储在数据库中
Bcrypt,Scrypt,pdkdf2等
2.1 常用的JSR 380验证注解
@NotNull : 验证注解属性值不为空。
@AssertTrue : 验证注解属性值是否为真
@Size : 验证注解的大小介于属性min和max之间
@Min : 验证注解属性的值不小于值属性的值
@Max : 验证被注解的值不大于值属性的值
@Email : 验证注解的属性是一个有效的电子邮件地址
@Pattern : 验证注解的属性是否匹配正则表达式
@NotEmpty : 验证属性不是空或空;应用于string
@NotBlank : 只能用于文本值,验证属性不是空的
@Positive : 使用于数值,验证它们是严格意义上的正数
@Negative : 适用于数值,验证他们是严格意义上的负值
@Past : 验证一个日期值是在过去
@Future : 验证一个日期值是在未来
2.2 自定义验证
- 自定义邮件注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EmailValidator.class)
public @interface ValidEmail {
String message () default "Invalid Email";
Class<?> [] groups() default {};
Class<? extends Payload>[] payload() default {};
}
- 自定义邮件注解配置,利用正则表达式进行解析定义验证逻辑
public class EmailValidator implements ConstraintValidator<ValidEmail,String> {
// 自定义正则表达式
private final static String EMPTY_PATTERN="";
@Override
public void initialize(ValidEmail constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
return validateEmail(s);
}
private boolean validateEmail(final String email){
// 对正则表达式进行解析
Pattern pattern = Pattern.compile(EMPTY_PATTERN);
Matcher matcher = pattern.matcher(email);
return matcher.matches();
}
}
2.3 密码的验证规则
密码的验证比较复杂,使用Passay框架进行验证,封装验证逻辑在注解中有效的剥离验证逻辑和业务逻辑
- 引入passay
<dependency>
<groupId>org.passay</groupId>
<artifactId>passay</artifactId>
<version>${passay.version}</version>
</dependency>
- 自定义密码验证
@Target({ElementType.FIELD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordConstraintValidator.class)
public @interface ValidPassword {
String message () default "Invalid Password";
Class<?> [] groups() default {};
Class<? extends Payload>[] payload() default {};
}
- 使用passay自定义验证逻辑
public class PasswordConstraintValidator implements ConstraintValidator<ValidPassword,String> {
@Override
public void initialize(ValidPassword constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(String password, ConstraintValidatorContext context) {
PasswordValidator validator = new PasswordValidator(
Arrays.asList(
//定义长度 6-20
new LengthRule(6,20),
//定义最少拥有一个英文字符
new CharacterRule(EnglishCharacterData.Alphabetical,1),
//定义至少拥有一个特殊字符
new CharacterRule(EnglishCharacterData.Special,1),
//禁止5个连续的字符
new IllegalSequenceRule(EnglishSequenceData.Alphabetical,5,false),
//禁止五个连续的数字
new IllegalSequenceRule(EnglishSequenceData.Numerical,5,false),
// 不允许空格
new WhitespaceRule()));
RuleResult result = validator.validate(new PasswordData(password));
if (result.isValid()) {
return true;
}
//设置校验失败后的消息
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(String.join(",", validator.getMessages(result)))
.addConstraintViolation();
return false;
}
}
2.4 比较dto中两个字段是否相等(如 密码,以及再次输入密码)
- 定义密码验证逻辑
public class PasswordMatchesValidator implements ConstraintValidator<PassWordMatches, UserDto> {
@Override
public void initialize(final PassWordMatches constraintAnnotation) { }
@Override
public boolean isValid(final UserDto obj, final ConstraintValidatorContext context) {
UserDto user = (UserDto) obj;
return user.getPassword().equals(user.getMatchPassword());
}
}
2.5 异常的统一处理
使用网上开源zalando
<dependency>
<groupId>org.zalando</groupId>
<artifactId>problem-spring-web</artifactId>
</dependency>
3 Spring Security 框架解析
基于数据库认证,实现一个可定制化的基础模型。
spring security 相关的单元测试。
3.1 核心组件
SecurityContext
- 用来存储当前认证的用户的详细信息
SecurityContextHolader
- 是一个工具类,提供了对安全上下文的访问,默认情况下使用ThreadLocal 对象来存储安全上下文
Authentication
- 存储了当前用户的详细信息
- Principal 可以理解为用户信息
- credentials 可以理解为密码
- Authorities 可以理解为权限
UserDetails在这里代表用户,而UserDetailsService 代表从用户数据库中调取用户形成UserDetails,这两个对象不负责认证工作只是提供数据,一般通过扩展他们来实现自定义的数据库结构
3.2 userDetails 和userDetailsService
- userDetails代表用户,具备一系列属性约束
- 从数据存储中根据用户名找到用户,是由UserDetailsService定义的
本身不负责认证,只是提供数据,一般通过扩展他们来实现自定义的数据库结构
扩展方式
-
系统内建了jdbcuserDetailsManager,可以通过构造不同的sql进行深度的定制化。
-
也可以实现自己的userDetails和userDetailsService 实现定制