Shiro - 认证和授权那些事

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/J080624/article/details/83380718

【1】认证

① 身份认证

身份认证是第一道门户,进去之后才能谈论授权的问题。

身份验证,一般需要提供如身份ID 等一些标识信息来表明登录者的身份,如提供email,用户名/密码来证明。

在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份:

  • principals:身份,即主体的标识属性,可以是任何属性,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/邮箱/手机号。
  • credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。

最常见的principals 和credentials 组合就是用户名/密码了。


② 身份认证流程

1)收集用户身份/凭证,即如用户名/密码;

2)调用Subject.login进行登录,如果失败将得到相应的AuthenticationException异常,根据异常提示用户错误信息;否则登录成功;

第2)步细节如下:

  • 创建自定义的Realm 类,继承org.apache.shiro.realm.AuthorizingRealm类,实现doGetAuthenticationInfo() 方法;
  • Shiro将会调用自定义的Realm 类的doGetAuthenticationInfo()方法,然后正常返回或者抛出异常;

如下图所示:

在这里插入图片描述

如下示例:

在这里插入图片描述


③ 认证过程详解

步骤如下:

  • 首先调用Subject.login(token) 进行登录,其会自动委托给SecurityManager
  • SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator 进行身份验证;
  • Authenticator 才是真正的身份验证者,ShiroAPI 中核心的身份认证入口点,此处可以自定义插入自己的实现;
  • Authenticator 可能会委托给相应的AuthenticationStrategy进行多Realm 身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm 身份验证;
  • Authenticator 会把相应的token 传入Realm(通常是你的自定义CustomRealm.doGetAuthenticationInfo()),从Realm 获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。

④ AuthenticationException

如果身份验证失败请捕获AuthenticationException或其子类。最好使用如“用户名/密码错误”而不是“用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库。

在这里插入图片描述


④ 代码示例

登录方法如下:

	 @RequestMapping(value="/doLogin" )
    public String doLogin(Model model, HttpSession session, HttpServletRequest request, HttpServletResponse response, String  userName, String password, String randomCode) throws Exception{
        String msg;
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
        log.debug("UserNamePasswordToken----:"+JSON.toJSONString(token));
        token.setRememberMe(true);
        request.setAttribute("userName", userName);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
            if (subject.isAuthenticated()) {
                return "redirect:/index";
            }
        } catch (UserNameException e) {
        	msg = e.getMessage();//Password for account " + token.getPrincipal() + " was incorrect.
        	model.addAttribute("message", msg);
        } catch (PasswordException e) {
        	msg = e.getMessage();//Password for account " + token.getPrincipal() + " was incorrect.
        	model.addAttribute("message", msg);
        } catch (IncorrectCredentialsException e) {
            msg = "登录密码错误. ";//Password for account " + token.getPrincipal() + " was incorrect.
            model.addAttribute("message", msg);
        } catch (ExcessiveAttemptsException e) {
            msg = "登录失败次数过多.";
            model.addAttribute("message", msg);
        } catch (LockedAccountException e) {
            msg = "帐号已被锁定,如有疑问请联系管理员. ";//The account for username " + token.getPrincipal() + " was locked.
            model.addAttribute("message", msg);
        } catch (DisabledAccountException e) {
            msg = "帐号已被禁用. ";//The account for username " + token.getPrincipal() + " was disabled.
            model.addAttribute("message", msg);
        } catch (ExpiredCredentialsException e) {
            msg = "帐号已过期. ";//the account for username " + token.getPrincipal() + "  was expired.
            model.addAttribute("message", msg);
        } catch (UnknownAccountException e) {
            msg = "帐号不存在. ";//There is no user with username of " + token.getPrincipal()
            model.addAttribute("message", msg);
        } catch (UnauthorizedException e) {
            msg = "您没有得到相应的授权!" + e.getMessage();
            model.addAttribute("message", msg);
        }
        return "forward:/login";
    }

自定义CustomRealm.doGetAuthenticationInfo()方法如下:

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken authenticationToken) throws AuthenticationException {
		 	UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
	        //获取页面传来的用户账号
		 	String loginName = token.getUsername();
	        //根据登录账号从数据库查询用户信息
	        SysUser user = sysUserService.getUserByLoginCode(loginName);
	        System.out.println("从数据库查询到的用户信息 : "+user);
	        //一些异常新娘西
	        if (null == user) {
	        	throw new UnknownAccountException();//没找到帐号
	        }
	        if (user.getStatus()==null||user.getStatus()==0) {
	        	throw new LockedAccountException();//帐号被锁定
	        }
	        //其他异常...
	        
	        //返回AuthenticationInfo的实现类SimpleAuthenticationInfo
	        return new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
	}

需要注意的是在登录成功后,注销登录一定要走Shiro过滤器 logout:

<!--请求logout,shrio擦除sssion-->
 /logout=logout

或者自定义注销过滤器,总之要完成如下的功能:任何现有的Session都将会失效,而且任何身份都将会失去关联。在web应用程序中,RememberMe cookie也将被删除。

否则,虽然注销成功了,Shiro缓存中还认为该用户认证成功,用户访问需要认证的连接时,将会直接通过!


猜你喜欢

转载自blog.csdn.net/J080624/article/details/83380718