Realm 域

即安全数据源

一般继承 AuthorizingRealm(授权)即可;其继承了 AuthenticatingRealm(即身份验证),
而且也间接继承了 CachingRealm(带有缓存实现)。

权限系统实体类定义

用户实体包括:编号(id)、用户名(username)、密码(password)、盐(salt)、是否锁定(locked);

是否锁定用于封禁用户使用,其实最好使用 Enum 字段存储,可以实现更复杂的用户状态实现。

package com.cxy.pojo;

public class User {
    private Long id;

    private String name;

    private String password;

    private String salt;
    
    
    //private String isLocked;是否锁定
    
    private UserStateEnum state = UserStateEnum.NORMAL;//默认正常状态

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name == null ? null : name.trim();
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password == null ? null : password.trim();
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt == null ? null : salt.trim();
    }
}


角色实体包括:编号(id)、角色标识符(role)、描述(description)、是否可用(available);
其中角色标识符用于在程序中进行隐式角色判断的,描述用于以后再前台界面显示的、是否可用表示角色当前是否激活。

package com.cxy.pojo;

public class Role {
    private Long id;

    private String name;


    private String desc_;//描述
    
   
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name == null ? null : name.trim();
    }

    public String getDesc_() {
        return desc_;
    }

    public void setDesc_(String desc_) {
        this.desc_ = desc_ == null ? null : desc_.trim();
    }
}


权限实体包括:编号(id)、权限标识符(permission)、描述(description)、对应的url(url),是否可用(available);
 

package com.cxy.pojo;

public class Permission {
    private Long id;

    private String name;

    private String desc_;

    private String url;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name == null ? null : name.trim();
    }

    public String getDesc_() {
        return desc_;
    }

    public void setDesc_(String desc_) {
        this.desc_ = desc_ == null ? null : desc_.trim();
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url == null ? null : url.trim();
    }
}

另外还有两个关系实体:

用户-角色实体(id,用户id、角色id);

package com.cxy.pojo;

public class UserRole {
    private Long id;

    private Long uid;

    private Long rid;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getUid() {
        return uid;
    }

    public void setUid(Long uid) {
        this.uid = uid;
    }

    public Long getRid() {
        return rid;
    }

    public void setRid(Long rid) {
        this.rid = rid;
    }
}

角色-权限实体(id,角色id、权限id)

package com.cxy.pojo;

public class RolePermission {
    private Long id;

    private Long rid;

    private Long pid;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getRid() {
        return rid;
    }

    public void setRid(Long rid) {
        this.rid = rid;
    }

    public Long getPid() {
        return pid;
    }

    public void setPid(Long pid) {
        this.pid = pid;
    }

定义Realm 

1、继承的父类 AuthorizingRealm 将获取 Subject 相关信息分成两步:获取身份验证信息(doGetAuthenticationInfo)及授权信息(doGetAuthorizationInfo);

2、doGetAuthenticationInfo 获取身份验证相关信息:首先根据传入的用户名获取 User 信
息;然后如果 user 为空,那么抛出没找到帐号异常 UnknownAccountException;如果 user
找到但锁定了抛出锁定异常 LockedAccountException;最后生成 AuthenticationInfo 信息,
交给间接父类 AuthenticatingRealm 使用 CredentialsMatcher 进行判断密码是否匹配,如果不
匹配将抛出密码错误异常 IncorrectCredentialsException;另外如果密码重试此处太多将抛出
超出重试次数异常 ExcessiveAttemptsException;

当然,实际中如果无特殊要求,直接抛出AuthenticationException,不给坏人机会

在组装 SimpleAuthenticationInfo 信息时,
需要传入:身份信息(用户名)、凭据(密文密码)、盐(salt),CredentialsMatcher
使用盐加密传入的明文密码和此处的密文密码进行匹配。
3、doGetAuthorizationInfo 获取授权信息:PrincipalCollection 是一个身份集合,因为我们
现在就一个 Realm,所以直接调用 getPrimaryPrincipal 得到之前传入的用户名即可;然后根
据用户名调用 UserService 接口获取角色及权限信息。

package com.cxy.realm;

import java.util.Set;

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.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import com.cxy.service.PermissionService;
import com.cxy.service.RoleService;
import com.cxy.service.UserService;

import md5.Md5Test;



/**
 * 安全认证实现类,获取Subject
 * @author cxy
 *
 */
 

 
public class DatabaseRealm extends AuthorizingRealm {
 
    @Autowired
    private UserService userService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private PermissionService permissionService;
     
    //获取授权信息
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //能进入到这里,表示账号已经通过验证了
        String userName =(String) principalCollection.getPrimaryPrincipal();
        //通过service获取角色和权限
        Set<String> permissions = permissionService.listPermissions(userName);
        Set<String> roles = roleService.listRoleNames(userName);
         
        //创建授权对象
        SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
        //把通过service获取到的角色和权限放进去
        s.setStringPermissions(permissions);
        s.setRoles(roles);
        return s;
    }
    
    //获取身份验证信息
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取账号密码
        UsernamePasswordToken t = (UsernamePasswordToken) token;
        String userName= token.getPrincipal().toString();
 
        
        //获取数据库中的密码
        String passwordInDB = userService.getPassword(userName);
        String salt = userService.getByName(userName).getSalt();
        
        //如果为空就是账号不存在,如果不相同就是密码错误,但是都抛出AuthenticationException,而不是抛出具体错误原因,免得给破解者提供帮助信息
        if(null==passwordInDB )
        { 
        	throw new AuthenticationException();
        }
        //认证信息里存放账号密码, 
        //用户名是AuthenticationToken传过来的用户名
		//密码是数据库查询的密码
        //getName() 是当前Realm的继承方法,通常返回当前类名 :databaseRealm
        //交由spring-context-shiro.xml的HashedCredentialsMatcher校验
        SimpleAuthenticationInfo a = new SimpleAuthenticationInfo(userName,passwordInDB,ByteSource.Util.bytes(salt),getName());
        return a;
    }
 

}

关于 AuthenticationToken

包含了身份(如用户名)及凭据(如密码): 

public interface AuthenticationToken extends Serializable { 
Object getPrincipal(); //身份
 Object getCredentials(); //凭据
}  

 扩展接口 RememberMeAuthenticationToken:提供了“boolean isRememberMe()  记住我的功能

扩展接口是 HostAuthenticationToken:提供了“String getHost()”方法用于获取用户“主机”的功能

Shiro 提供了一个直接拿来用的 UsernamePasswordToken,用于实现用户名/密码 Token 组,
另外其实现了 RememberMeAuthenticationToken 和 HostAuthenticationToken,可以实现记住
我及主机验证的支持,只要进行   UsernamePasswordToken t = ( UsernamePasswordToken )token ,转换下就行了

AuthenticationInfo 

1、如果 定义的xxxRealm 继承了 AuthenticatingRealm ,则提供给 AuthenticatingRealm 内部使用的
CredentialsMatcher 进行凭据验证;(如果没有继承它需要在自己的 Realm 中自己实现验证);
2、提供给 SecurityManager 来创建 Subject(提供身份信息);
MergableAuthenticationInfo 用于提供在多 Realm 时合并 AuthenticationInfo 的功能,主要合
并 Principal、如果是其他的如 credentialsSalt,会用后边的信息覆盖前边的。
比 如 HashedCredentialsMatcher , 在 验 证 时 会 判 断 AuthenticationInfo 是 否 是
SaltedAuthenticationInfo 子类,来获取盐信息。
Account 相当于我们之前的 User,SimpleAccount 是其一个实现;在 IniRealm、PropertiesRealm
这种静态创建帐号信息的场景中使用,这些 Realm 直接继承了 SimpleAccountRealm,而
SimpleAccountRealm 提供了相关的 API 来动态维护 SimpleAccount;即可以通过这些 API
来动态增删改查 SimpleAccount;动态增删改查角色/权限信息。及如果您的帐号不是特别
多,可以使用这种方式,具体请参考 SimpleAccountRealm Javadoc。
其他情况一般返回 SimpleAuthenticationInfo 即可。 

PrincipalCollection

截止到目前为止,只定义了一个Realm,String userName =(String) principalCollection.getPrimaryPrincipal();自然取到了我们想要的身份信息

但是我们可以在 Shiro 中同时配置多个 Realm,所以呢身份信息可能就有多个;因此其提供了 PrincipalCollection 用于聚合这些身份信息:

public interface PrincipalCollection extends Iterable, Serializable { 
Object getPrimaryPrincipal(); //得到主要的身份
<T> T oneByType(Class<T> type); //根据身份类型获取第一个
<T> Collection<T> byType(Class<T> type); //根据身份类型获取一组
List asList(); //转换为 List 
Set asSet(); //转换为 Set 
Collection fromRealm(String realmName); //根据 Realm 名字获取
Set<String> getRealmNames(); //获取所有身份验证通过的 Realm 名字
 boolean isEmpty(); //判断是否为空
} 

因为 PrincipalCollection 聚合了多个,此处最需要注意的是 getPrimaryPrincipal,如果只有一
个 Principal 那么直接返回即可,如果有多个 Principal,则返回第一个(因为内部使用 Map
存储,所以可以认为是返回任意一个);

oneByType / byType 根据凭据的类型返回相应的Principal;

fromRealm 根据 Realm 名字(每个 Principal 都与一个 Realm 关联)获取相应的Principal。

多Realm使用场景

比如不同角色,使用不同的登录策略

基于xml

基于springboot

AuthorizationInfo 授权对象

AuthorizationInfo 用于聚合授权信息的:

public interface AuthorizationInfo extends Serializable { 
 Collection<String> getRoles(); //获取角色字符串信息
 Collection<String> getStringPermissions(); //获取权限字符串信息
 Collection<Permission> getObjectPermissions(); //获取 Permission 对象信息
} 


当 我 们 使 用 AuthorizingRealm 时 , 如 果 身 份 验 证 成 功 , 在 进 行 授 权 时 就 通 过
doGetAuthorizationInfo 方法获取角色/权限信息用于授权验证。
Shiro 提供了一个实现 SimpleAuthorizationInfo,大多数时候使用这个即可

Subject

Subject 是 Shiro 的核心对象,基本所有身份验证、授权都是通过 Subject 完成

对于 Subject 的构建一般没必要我们去创建;一般通过 SecurityUtils.getSubject()获取

源码:即首先查看当前线程是否绑定了 Subject,如果没有通过 Subject.Builder 构建一个然后绑定到现场返回

public static Subject getSubject() { 
 Subject subject = ThreadContext.getSubject(); 
 if (subject == null) { 
 subject = (new Subject.Builder()).buildSubject(); 
 ThreadContext.bind(subject); 
 } 
 return subject; 
}

用法:

1、身份信息获取 

Object getPrincipal(); //Primary Principal 
PrincipalCollection getPrincipals(); // PrincipalCollection

2、身份验证

void login(AuthenticationToken token) throws AuthenticationException; 
boolean isAuthenticated(); 
boolean isRemembered(); 

注明:通过 login 登录,如果登录失败将抛出相应的 AuthenticationException,如果登录成功调用
isAuthenticated 就会返回 true,即已经通过身份验证;如果 isRemembered 返回 true,表示是
通过记住我功能登录的而不是调用 login 方法登录的。isAuthenticated/isRemembered 是互斥
的,即如果其中一个返回 true,另一个返回 false。

3.角色授权验证

boolean hasRole(String roleIdentifier); 
boolean[] hasRoles(List<String> roleIdentifiers); 
boolean hasAllRoles(Collection<String> roleIdentifiers); 
void checkRole(String roleIdentifier) throws AuthorizationException; 
void checkRoles(Collection<String> roleIdentifiers) throws AuthorizationException; 
void checkRoles(String... roleIdentifiers) throws AuthorizationException; 

hasRole*进行角色验证,验证后返回 true/false;

而 checkRole*验证失败时抛出AuthorizationException 异常 ,别瞎用,会造成无法登陆

4、权限授权验证

boolean isPermitted(String permission); 
boolean isPermitted(Permission permission); 
boolean[] isPermitted(String... permissions); 
boolean[] isPermitted(List<Permission> permissions); 
boolean isPermittedAll(String... permissions); 
boolean isPermittedAll(Collection<Permission> permissions); 
void checkPermission(String permission) throws AuthorizationException; 
void checkPermission(Permission permission) throws AuthorizationException; 
void checkPermissions(String... permissions) throws AuthorizationException; 
void checkPermissions(Collection<Permission> permissions) throws AuthorizationException; 

isPermitted*进行权限验证,验证后返回 true/false;而 checkPermission*验证失败时抛出
AuthorizationException。和角色授权认证类似. 

5、会话

Session getSession(); //相当于 getSession(true) 
Session getSession(boolean create); 

登录成功就创建了一个会话,Session session  = subject.getSession()获取
如果 create=true 如果没有会话将返回 null,而 create=true 如果没有会话会强制创建一个。 

6.退出

void logout();

7、RunAs 

void runAs(PrincipalCollection principals) throws NullPointerException, IllegalStateException; 
boolean isRunAs(); 
PrincipalCollection getPreviousPrincipals(); 
PrincipalCollection releaseRunAs(); 

RunAs 即实现“允许 A 假设为 B 身份进行访问”;通过调用 subject.runAs(b)进行访问;接
着调用 subject.getPrincipals 将获取到 B 的身份;此时调用 isRunAs 将返回 true;而 a 的身
份需要通过 subject. getPreviousPrincipals 获取;如果不需要 RunAs 了调用 subject. 
releaseRunAs 即可

8、多线程

<V> V execute(Callable<V> callable) throws ExecutionException; 
void execute(Runnable runnable); 
<V> Callable<V> associateWith(Callable<V> callable); 
Runnable associateWith(Runnable runnable); 

 实现线程之间的 Subject 传播,因为 Subject 是线程绑定的;因此在多线程执行中需要传播
到相应的线程才能获取到相应的 Subject。最简单的办法就是通过 execute(runnable/callable
实例)直接调用;或者通过 associateWith(runnable/callable 实例)得到一个包装后的实例;它
们都是通过:1、把当前线程的 Subject 绑定过去;2、在线程执行结束后自动释放。

Subject 自己不会实现相应的身份验证/授权逻辑,而是通过 DelegatingSubject 委托给
SecurityManager 实现;及可以理解为 Subject 是一个面门

代码已上传github  :https://github.com/currynice/shiro-ssm

猜你喜欢

转载自blog.csdn.net/qq_38930240/article/details/86673908
今日推荐