自学-Shiro的身份认证-05

   学习了前几节,大家可能只是对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{

	@Override
	protected 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 对象的 name
		String 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:新手自学,哪里不对还望指出,谢谢。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值