发表于: 2020-08-08 23:40:35

1 2246


接着学习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;
}

身份校验还没写

明日计划 深度思考 完善代码


返回列表 返回列表
评论

    分享到