发表于: 2020-08-08 23:40:35
1 2247
接着学习shiro
credentials -- shiro密码加密验证服务类CredentialsMatcher,用于登录错误次数限制
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {
private Cache<String, AtomicInteger> passwordRetryCache;
public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager){
passwordRetryCache = cacheManager.getCache("passwordRetryCache");
}
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
String username = (String) token.getPrincipal();
//return count+1
AtomicInteger retryCount = passwordRetryCache.get(username);
if(retryCount == null){
retryCount = new AtomicInteger(0);
passwordRetryCache.put(username,retryCount);
}
if(retryCount.incrementAndGet() > 5){
throw new ExcessiveAttemptsException();
}
boolean matches = super.doCredentialsMatch(token,info);
if(matches){
//clear retry count
passwordRetryCache.remove(username);
}
return matches;
}
}
shiro的拦截器
默认拦截器名 | 说明 |
---|---|
authc | 基于表单的拦截器,比如若用户没有登录就会跳转到loginUrl 的地址,其拦截的请求必须是通过登录验证的,即Subject.isAuthenticated() == true 的账户才能访问 |
anon | 匿名拦截器,和authc 拦截器刚好作用相反。anon 配置的请求允许用户为登录就等访问,一般我们配置登录页面和静态CSS等资源是允许匿名访问 |
logout | 退出拦截器,Shiro提供了一个退出的功能,配置了/logout = logout ,Shiro就会生成一个虚拟的映射路径,当用户访问了这个路径,Shiro会自动清空缓存并跳转到loginUrl 页面 |
user | 用户拦截器,和authc 拦截器很类似,都是账户为登录的进行拦截并跳转到loginUrl 地址;不同之处在于authc 允许账户必须是通过Subject.siAuthenticated() ==true 的;而user 不仅允许登录账户访问,通过rememberMe登录的用户也能访问 |
身份认证的流程
如果用户为登录,将跳转到loginUrl进行登录,登录表单中,包含了两个主要参数:用户名username、密码password(这两个参数名称不是固定的,但是要和FormAuthenticationFilter表单过滤器的参数配置要对应)。
用户输入这两个用户名和密码后提交表单,通过绑定了SecurityManager的SecurityUtils得到Subject实例,然后获取身份验证的UsernamePasswordToken传入用户名和密码。
调用subject.login(token)进行登录,SecurityManager会委托Authenticator把相应的token传给Realm,从Realm中获取身份认证信息。
Realm可以是自己实现的Realm,Realm会根据传入的用户名和密码去数据库进行校验(提供Service层登录接口)。
Shiro从Realm中获取安全数据(如用户、身份、权限等),如果校验失败,就会抛出异常,登录失败;否则就登录成功。
public String login(
@RequestParam(value = "username", required = false) String username,
@RequestParam(value = "password", required = false) String password,
@RequestParam(value = "remember", required = false) String remember,
Model model) {
System.out.println("登陆用户输入的用户名:" + username + ",密码:" + password);
String error = null;
if (username != null && password != null) {
//初始化
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
if (remember != null){
if (remember.equals("on")) {
//说明选择了记住我
token.setRememberMe(true);
} else {
token.setRememberMe(false);
}
}else{
token.setRememberMe(false);
}
try {
//登录,即身份校验,由通过Spring注入的UserRealm会自动校验输入的用户名和密码在数据库中是否有对应的值
subject.login(token);
System.out.println("用户是否登录:" + subject.isAuthenticated());
return "redirect:index.do";
} catch (UnknownAccountException e) {
e.printStackTrace();
error = "用户账户不存在,错误信息:" + e.getMessage();
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
error = "用户名或密码错误,错误信息:" + e.getMessage();
} catch (LockedAccountException e) {
e.printStackTrace();
error = "该账号已锁定,错误信息:" + e.getMessage();
} catch (DisabledAccountException e) {
e.printStackTrace();
error = "该账号已禁用,错误信息:" + e.getMessage();
} catch (ExcessiveAttemptsException e) {
e.printStackTrace();
error = "该账号登录失败次数过多,错误信息:" + e.getMessage();
} catch (Exception e){
e.printStackTrace();
error = "未知错误,错误信息:" + e.getMessage();
}
} else {
error = "请输入用户名和密码";
}
//登录失败,跳转到login页面
model.addAttribute("error", error);
return "login";
}
当login()映射方法得到用户输入的用户名和密码后调用subject.login(token)进行登录,随后就是通过Realm进行登录校验,如果登录失败就可能抛出一系列异常,比如UnknownAccountException用户账户不存在异常、IncorrectCredentialsException用户名或密码错误异常、LockedAccountException账户锁定异常...
示例中在Controller层中没有处理登录成功,而是在ShiroFilterFactoryBean中配置successUrl,很多博文中讲到:如果登录成功Shiro会自动跳转到登录前访问的地址,如果找不到登录前访问的地址,就会跳转到successUrl中配置的地址;But,我在测试中并没有看到这种特性
自定义realm
属性名称 | 作用 |
---|---|
principals | 身份,主体的唯一标识,比如用户名、邮箱等,如果你将用户名和密码传给了Token对象,那么在Token对象中就能getPrincipal 获取这个标识 |
credentials | 证明、凭证。比如密码、数字证书等。但是在Shiro等安全框架中,类似于密码这种数据一般都是经过加密处理的,它肯能不单单是密码的数据, |
Shiro从Realm中获取安全数据,我们可以自定义多个Realm实现,但都要在SecurityManager中定义。一般我们自定义实现的Realm继承AuthorizingRealm(授权)即可,它继承了AuthenticatingRealm(身份验证);所以自定义Realm一般存在两个最主要的功能:1.身份验证;2.权限校验。
在用户登录后,Controller会接收到用户输入的用户名和密码,并调用subject.login(token)
进行登录,实际上SecurityManager会委托Authenticator
调用自定义的Realm进行身份验证。要知道,调用Realm传入的并不直接是用户名和密码,而是在Controller中绑定了用户名和密码的Token对象
/**
* 权限校验
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
return authorizationInfo;
}
身份校验还没写
明日计划 深度思考 完善代码
评论