验证的理论过程
1、先获取当前的subject
//获取当前的subject
Subject currentUser = SecurityUtils.getSubject();
2、校验当前的subject是否已经被认证
currentUser.isAuthenticated()
3、若没有被认证
把用户名和密码封装为UsernamePasswordToken对象
-
创建一个表单页面
-
将请求提交到spring mvc的handler对象
-
获取用户名和密码
4、执行登录。执行subject的login(AuthenticationToken)方法
5、自定义Realm方法,从数据库中获取对应的记录,返回给shiro
-
实际上需要继承org.apache.shiro.realm.AuthorizingRealm类
-
实现doGetAuthenticationInfo(AuthenticationToken authcToken)方法
6、由shiro完成对密码的比对
补充:
1)密码的比对是通过AuthorizingRealm的 credetialsMatcher属性来进行的密码比对
2)加密对比:
1、如何将一个字符串密码进行加密成MD5
2、替换当前Realm的credentialsMatcher属性,直接使用HashedCredentialsMatcher进行加密设置
操作实现
1、创建一个login.jsp
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>登录页面</title>
</head>
<body>
<h1>login</h1>
<form action="shiro/login" method="post">
username:<input type = "text" name = "username"/><br/><br/>
password:<input type = "text" name = "password"/><br/><br/>
<input type="submit" value = "submit">
</form>
</body>
</html>
2、创建一个handler(类似controller)
package com.example.handlers;
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.RequestParam;
@Controller
@RequestMapping("/shiro")
public class ShiroHandler {
/**
* 登录方法,登录成功返回success页面
*
* @param username:用户名
* @param password:密码
* @return
*/
@RequestMapping("/login")
public String login(@RequestParam("username") String username, @RequestParam("password") String password) {
Subject currentUser = SecurityUtils.getSubject();
// 测试当前的用户是否登录,也就是是否被认证
if (!currentUser.isAuthenticated()) {
// 把用户名和密码封装为UsernamePasswordToken对象
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// RememberMe
token.setRememberMe(true);
try {
// 执行登录
currentUser.login(token);
}
// 认证的所有异常处理
catch (AuthenticationException ae) {
System.out.println("登录失败:"+ae.toString());
}
}
return "redirect:/success.jsp";
}
}
3、创建一个Realm,来实现登录(要在applicationContext-shiro中进行配置)
继承AuthenticatingRealm接口(若只实现认证)
继承AuthorizingRealm接口(实现认证和授权)
public class ShiroRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
return null;
}
//认证回调函数
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("doGetAuthenticationInfo:"+token);
//1、把AuthenticationToken强转为UsernamePasswordToken
UsernamePasswordToken t = (UsernamePasswordToken) token;
//2、从t中获取用户名和密码
String username = t.getUsername();
//3、调用数据库的方法,从数据库查询对应的用户记录
System.out.println("从数据库中取数据:"+username+"的用户信息为");
//3.1小试一下就是来静态数据
//4、若用户名不存在,抛出异常
if (username.equals("unknown")) {
System.out.println("登录失败,用户不存在");
throw new UnknownAccountException("用户不存在");
}
//5、根据用户信息的情况,看是否抛出其他异常
if (username.equals("status_disable")) {
System.out.println("登录失败,用户被禁用");
throw new LockedAccountException("用户被禁用");
}
//6、根据用户的情况,来构建AuthenticationToken对象并返回
//6.1以下信息是从数据库中获取的
//principals:认证的实体信息,可以是数据库中的实体类对象,可以是username
Object principals = username;
//credentials:从数据库获取的密码
Object credentials = "123456";
//realmName:当前realm对象的name,直接调用父类的getName()方法即可
String realmName = getName();
SimpleAuthenticationInfo simInfo = new SimpleAuthenticationInfo(principals, credentials,realmName);
return simInfo;
}
}
4、拦截设置(登录请求被拦截了)(在applicationContext-shiro配置中设置的)
<property name="filterChainDefinitions">
<value>
/login.jsp = anon
/shiro/login = anon
/shiro/loginout = logout
/** = authc
</value>
</property>
补充:(关于Realm的操作,对加密密码进行对比)
1、如何将一个字符串密码进行加密成MD5
2、替换当前Realm的credentialsMatcher属性,直接使用HashedCredentialsMatcher进行加密设置
在applicationContext-shiro.xml中配置Realm的时候进行配置
<!-- 3、配置realm,新建一个ShiroRealm类,实现Realm接口 -->
<bean id="jdbcRealm" class="com.example.realms.ShiroRealm" >
<property name="credentialsMatcher">
<bean class = "org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!-- 加密的算法 -->
<property name="hashAlgorithmName" value="MD5"></property>
<!-- 加密的次数 -->
<property name="hashIterations" value="1024"></property>
</bean>
</property>
</bean>
得知一个密码加密后的密码:
public static void main(String[] args) {
String hashAlgorithmName = "MD5";
Object credentials = "123456";
Object salt = null;
int hashIterations = 1024;
System.out.println("加密前的密码:"+credentials);
SimpleHash hash = new SimpleHash(hashAlgorithmName,credentials,salt,hashIterations);
System.out.println("加密后的密码:"+hash);
}
利用salt来实现盐值加密(两个用户密码相同,加密后的到的密码不同)
ByteSource.Util.bytes(username):一般使用唯一的那一个属性来进行加盐
//6、根据用户的情况,来构建AuthenticationToken对象并返回
//6.1以下信息是从数据库中获取的
//principals:认证的实体信息,可以是数据库中的实体类对象,可以是username
Object principals = username;
//credentials:从数据库获取的密码
Object credentials = "123456";
//realmName:当前realm对象的name,直接调用父类的getName()方法即可
String realmName = getName();
//salt来实现盐值加密,若username唯一,可以使用username来加盐
ByteSource credentialsSalt = ByteSource.Util.bytes(username)
SimpleAuthenticationInfo simInfo = new SimpleAuthenticationInfo(principals, credentials,credentialsSalt,realmName);
return simInfo;
多Realm
1、建立两个Realm(ShiroRealm与LoginRealm)
2、配置Realm(在applicationContext-shiro.xml文件中配置)
执行顺序会与配置文件中list中配置realm的顺序相同
<bean id="securityManager"
class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager" />
<property name="sessionMode" value="native" />
<property name="authenticator" ref="modularRealmAuthenticator" />
</bean>
<!-- 认证器 -->
<bean id="modularRealmAuthenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<property name="realms">
<list>
<ref bean="shiroRealm"/>
<ref bean="loginRealm"/>
</list>
</property>
</bean>
<!-- 3、配置realm,新建一个ShiroRealm类,实现Realm接口 -->
<bean id="shiroRealm" class="com.example.realms.ShiroRealm" >
<property name="credentialsMatcher">
<bean class = "org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!-- 加密的算法 -->
<property name="hashAlgorithmName" value="MD5"></property>
<!-- 加密的次数 -->
<property name="hashIterations" value="1024"></property>
</bean>
</property>
</bean>
<bean id="loginRealm" class="com.example.realms.LoginRealm" >
<property name="credentialsMatcher">
<bean class = "org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!-- 加密的算法 -->
<property name="hashAlgorithmName" value="SHA1"></property>
<!-- 加密的次数 -->
<property name="hashIterations" value="1024"></property>
</bean>
</property>
</bean>
认证策略 Authentication Strategy
Authentication Strategy接口的默认实现
-
FirstSuccessfulStrategy:只要有一个Ream验证成功即可,只返回笫一个Ream身份验证成功的认证信息,其他的忽略;
-
AtLeastoneSuccessfulStrategy:只要有一个Ream验证成功即可,和FirstSuccessful Strategy不同,将返回所有 Realm身份验证成功的认证信息
-
AllSuccessfulStrategy:所有Ream验证或功才算成功,且返回所有Ream身份验证成功的认证信息,如果有个失败就失败了。
默认是 AtLeastone SuccessfulStrateagy策略
<!-- 认证器 -->
<bean id="modularRealmAuthenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<property name="realms">
<list>
<ref bean="shiroRealm"/>
<ref bean="loginRealm"/>
</list>
</property>
<!--该属性来修改认证策略-->
<property name="authenticationStrategy" >
<bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"></bean>
</property>
</bean>
另一种方法就是全部写好,自己转换
<bean id="modularRealmAuthenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<property name="authenticationStrategy" ref="firstSuccessfulStrategy"></property>
</bean>
<bean id="atLeastOneSuccessfulStrategy" class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean><!-- 多realm策略:只要有一个(或更多)的Realm验证成功,那么认证将被视为成功 -->
<bean id="firstSuccessfulStrategy" class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"></bean><!-- 多realm策略:第一个Realm验证成功,整体认证将被视为成功,且后续Realm将被忽略 -->
<bean id="allSuccessfulStrategy" class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"></bean><!-- 多realm策略:所有Realm成功,认证才视为成功 -->
授权
授权,也叫访问控制,即在应用中控制谁访问哪些资源〔如访问页面/编辑数据/面操作,在授权中需了解的几个关键对象:主体( Subject)、资源( Resource)、权限( Permission)、角色(Role)
-
主体( Subject):访问应用的用户,在 Shiro中使用 Subject代表该用户。用户只有授权后才允许访问相应的资源。
-
资源( Resource):在应用中用户可以访问的URL,比如访问JSP页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
-
权限( Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用户列表页面查看/新增/修改倗删除用户数据〔即很多时候都是CRUD(增查改删)式权限控制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允限〔如用户模块的所有权限)和细粒度权限
-
角色(Role):一般情况下会赋予用户角色而不是权限,即这样用户可以拥有组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。
在securityManager与modularRealmAuthenticator都可以进行Realm的配置
若想进行授权,需要在securityManager中配置而不是在modularRealmAuthenticator中配置
<bean id="securityManager"
class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager" />
<property name="sessionMode" value="native" />
<property name="authenticator" ref="modularRealmAuthenticator" />
<property name="realms">
<list>
<ref bean="shiroRealm"/>
<ref bean="loginRealm"/>
</list>
</property>
</bean>
授权需要继承AuthorizingRealm类,同时实现认证和授权
//用于授权的方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
//1、从principal获取用户登录信息
Object primaryPrincipal = principal.getPrimaryPrincipal();
//2、利用登录的用户信息来获取当前用户的信息和权限
Set<String> roles = new HashSet<String>();
roles.add("user");
if(primaryPrincipal.equals("admin")) {
roles.add("a");
}
//3、利用SimpleAuthorizationInfo,并设置roles属性
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
//4、返回SimpleAuthorizationInfo对象
return info;
}
public class ShiroRealm extends AuthorizingRealm {
//用于授权的方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
//1、从principal获取用户登录信息
Object primaryPrincipal = principal.getPrimaryPrincipal();
//2、利用登录的用户信息来获取当前用户的信息和权限
Set<String> roles = new HashSet<String>();
roles.add("user");
if(primaryPrincipal.equals("admin")) {
roles.add("a");
}
//3、利用SimpleAuthorizationInfo,并设置roles属性
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
//4、返回SimpleAuthorizationInfo对象
return info;
}
//认证回调函数
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("第二个Relam:ShiroRealm");
//1、把AuthenticationToken强转为UsernamePasswordToken
UsernamePasswordToken t = (UsernamePasswordToken) token;
//2、从t中获取用户名和密码
String username = t.getUsername();
//3、调用数据库的方法,从数据库查询对应的用户记录
System.out.println("从数据库中取数据:"+username+"的用户信息为");
//3.1小试一下就是来静态数据
//4、若用户名不存在,抛出异常
if (username.equals("unknown")) {
System.out.println("登录失败,用户不存在");
throw new UnknownAccountException("用户不存在");
}
//5、根据用户信息的情况,看是否抛出其他异常
if (username.equals("status_disable")) {
System.out.println("登录失败,用户被禁用");
throw new LockedAccountException("用户被禁用");
}
//6、根据用户的情况,来构建AuthenticationToken对象并返回
//6.1以下信息是从数据库中获取的
//principals:认证的实体信息,可以是数据库中的实体类对象,可以是username
Object principals = username;
//credentials:从数据库获取的密码
Object credentials = "123456";
//realmName:当前realm对象的name,直接调用父类的getName()方法即可
String realmName = getName();
SimpleAuthenticationInfo simInfo = new SimpleAuthenticationInfo(principals, credentials,realmName);
return simInfo;
}
public static void main(String[] args) {
String hashAlgorithmName = "MD5";
Object credentials = "123456";
Object salt = null;
int hashIterations = 1024;
System.out.println("加密前的密码:"+credentials);
SimpleHash hash = new SimpleHash(hashAlgorithmName,credentials,salt,hashIterations);
System.out.println("加密后的密码:"+hash);
}
}