您的位置:首页 > 其它

[置顶] 自学-Shiro的身份认证-05

2016-12-15 17:20 225 查看
   学习了前几节,大家可能只是对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[b]核心的身份认证入口点。[/b]



/**
* 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:






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

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

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

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

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

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

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: