自学-Shiro的身份认证-05

来源:互联网 发布:淘宝卖家能屏蔽震动 编辑:程序博客网 时间:2024/04/30 04:01

   学习了前几节,大家可能只是对Shiro有个大概的了解,其实,Shiro的重点及难点都在后面的博客中,接下来的这节我们来探讨一下身份认证.

我们可以一起来看下身份认证流程,有个大概的思绪,在来一起写代码进行实现。

身份认证流程:


 

流程步骤(借鉴英文文档翻译):

1.首先调用Subject.login(token)进行登录,其会自动委托给Security Manager,调用之前必须通过SecurityUtils. setSecurityManager()设置;

2.SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证;

3.Authenticator才是真正的身份验证者,Shiro API中核心的身份认证入口点,此处可以自定义插入自己的实现;

4.Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证;

5.Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。

流程大概就这个样子,其实可以用更加通俗易懂的语言来描述更好。这些流程看源码是最清楚的,所以我们可以针对源码进行走一下,然后理解起来会更加的清楚明了。

代码进行一一解释

如下:

1.首先进行登录:

 

currentUser.login(token);
2.SecurityManager一个大管家。

public void login(AuthenticationToken token) throws AuthenticationException {        clearRunAsIdentitiesInternal();        Subject subject = securityManager.login(this, token);        PrincipalCollection principals;        String host = null;        if (subject instanceof DelegatingSubject) {            DelegatingSubject delegating = (DelegatingSubject) subject;            //we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:            principals = delegating.principals;            host = delegating.host;        } else {            principals = subject.getPrincipals();        }        if (principals == null || principals.isEmpty()) {            String msg = "Principals returned from securityManager.login( token ) returned a null or " +                    "empty value.  This value must be non null and populated with one or more elements.";            throw new IllegalStateException(msg);        }        this.principals = principals;        this.authenticated = true;        if (token instanceof HostAuthenticationToken) {            host = ((HostAuthenticationToken) token).getHost();        }        if (host != null) {            this.host = host;        }        Session session = subject.getSession(false);        if (session != null) {            this.session = decorate(session);        } else {            this.session = null;        }    }
3.Authenticator核心的身份认证入口点。

/**     * Delegates to the wrapped {@link org.apache.shiro.authc.Authenticator Authenticator} for authentication.     */    public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {        return this.authenticator.authenticate(token);    }

4.Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证。

 protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {        assertRealmsConfigured();        Collection<Realm> realms = getRealms();        if (realms.size() == 1) {//单个            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);        } else {//多个            return doMultiRealmAuthentication(realms, authenticationToken);        }    }


总而言之就是这样:

1.获取获取当前的 Subject. 调用 SecurityUtils.getSubject();

2.判断当前的用户是否已经被认证. 即是否已经登录. 调用 Subject 的 isAuthenticated() 。

3.若没有认证, 则把用户名和密码封装为 UsernamePasswordToken 对象。

4. 执行登录。调用 Subject 的login(token); 方法. 

注:token指代是:AuthenticationToken

5.自定义 Realm 的方法, 从数据库中获取对应的记录,并对密码进行加密,来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo。

即:

SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);

实例结构:


实例代码:

LoginController.java:

package com.yiyi.controller;import javax.servlet.http.HttpServletRequest;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.subject.Subject;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;/** * 登录的Controller * @author hanyiyi * */@Controller@RequestMapping("/shiro")public class LoginController {/** * 登录方法 * @param request * @return */@RequestMapping(value="/login",method=RequestMethod.POST)public String login(HttpServletRequest request){//获取用户名和密码String username = request.getParameter("username");String password=request.getParameter("password"); // 使用SecurityUtils.getSubject();来获取当前的 Subject.         Subject currentUser = SecurityUtils.getSubject();        //判断当前的用户是否已经被认证。        if(!currentUser.isAuthenticated()){        //若没被认证,则把用户名和密码封装为UsernamePasswordToken对象        UsernamePasswordToken token=new UsernamePasswordToken(username,password);         token.setRememberMe(true);             try {             // 执行登录.                  currentUser.login(token);             }              // ... catch more exceptions here (maybe custom ones specific to your application?             // 所有认证时异常的父类.              catch (AuthenticationException ae) {                 //unexpected condition?  error?             System.out.println("登录失败---->"+ae.getMessage());             }         }        return "redirect:/index.jsp";}}

自定义Realm:
MyRealm.java:

package com.yiyi.realm;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.LockedAccountException;import org.apache.shiro.authc.SimpleAuthenticationInfo;import org.apache.shiro.authc.UnknownAccountException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.realm.AuthenticatingRealm;public class MyRealm extends AuthenticatingRealm{@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//将AuthenticationToken对象转换成UsernamePasswordToken对象 UsernamePasswordToken upToken = (UsernamePasswordToken) token; //获取UsernamePasswordToken中的用户名 String username = upToken.getUsername(); //从数据库中查询 username 对应的用户记录                 System.out.println("从数据库中查找"+username+"的信息");                 //若用户的信息不存在,则抛出UnknownAccountException异常。 if("unknown".equals(username)){ throw new UnknownAccountException("用户不存在"); }//根据用户的信息进行反馈,则抛出LockedAccountException异常。 if("han".equals(username)){ throw new  LockedAccountException("用户被锁定"); } //根据用户的信息来封装SimpleAuthenticationInfo对象。 //当前 realm 对象的 nameString realmName = getName();//认证的实体信息。Object principal = username;//密码Object credentials="123456";SimpleAuthenticationInfo info =new SimpleAuthenticationInfo(principal, credentials, realmName);return info;}}

登录的页面:
login.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%><%String path = request.getContextPath();String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html>  <head>    <base href="<%=basePath%>">        <title>登录页面</title><meta http-equiv="pragma" content="no-cache"><meta http-equiv="cache-control" content="no-cache"><meta http-equiv="expires" content="0">    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"><meta http-equiv="description" content="This is my page">  </head>  <body>  <h4>login page</h4>  <form action="shiro/login" method="post">     username:<input type="text" name="username"> <br/><br/>     password:<input type="password" name="password"><br/><br/>               <input type="submit" value="提交">  </form>  </body></html>
拦截器配置;

先让shiro/login进行匿名访问:


图形化界面;


现在我们来执行下这个操作:

首先进入登录页面:

http://localhost:8080/Shiro-03/login.jsp

根据代码,我们先输入一个正确的用户名和密码:zhao  123456 这时候会进入到对应的页面,假如我们在一次进行入到登录页面,随意输入个错误的密码,这时候还是可以登录上的,这个是为什么呢?

原因是:shiro的缓存起到了作用,这时候避免出现这样的现象我们直接在applicationContext.xml中配置一个logou的拦截器即可。

index.jsp:

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%><%String path = request.getContextPath();String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html>  <head>    <base href="<%=basePath%>">        <title>My JSP 'index.jsp' starting page</title>    <meta http-equiv="pragma" content="no-cache"><meta http-equiv="cache-control" content="no-cache"><meta http-equiv="expires" content="0">    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"><meta http-equiv="description" content="This is my page">  </head>    <body>    index jsp    <a href="shiro/logout">登出</a>  </body></html>

applicationContext.xml:


这其中还有很多重点需要一一解释:

①:为什么自定义的Realm 为什么直接继承AuthenticatingRealm呢?

②:SimpleAuthenticationInfo 这个对象中的参数都指代什么呢?

③:shiro的密码怎么进行比对呢,怎么进行加密呢?

这些都到下一节进行详细的描述吧。

Ps:新手自学,哪里不对还望指出,谢谢。

 

0 0
原创粉丝点击