关于授权
- 一般来说用户通过认证后既可以访问服务获取资源,提交、更新信息,甚至删除信息
- 但是每个用户存在的环境不同,可以操作的内容也不同,如果所有用户都有权限去新增删除用户那就会乱套了,所以系统中的用户具有角色、部门甚至是岗位的描述。这种描述就是用来对用户做分级,达到不同用户的授权独立
- 简单的说。比如部门A的用户可以有查询的权限,部门B的用户可以有新增、更新的权限。两个部门用户的权限独立,如果再细分一些,部门B的用户分为insert角色,与update角色
- insert 角色可以做新增
- update 角色可以做更新
- 通过分级授权可以管理和明确权限的分级,保证了业务上的安全性和稳定性
- 授权一般非为操作权限和数据权限,上面说的是操作权限,数据权限就和业务紧密结合。
关于Shiro
- Shiro是由Apache基金会开发的开源JAVA权限管理框架,该框架可以很好的Spring整合。
- Shiro 官网
- Shiro包含了三个核心的组件
- 1.Subject 主体 官方文档
- 在Shiro中Subject不仅仅指人,它指代了所有和软件交互的实体,大部分情况下储存是用户的信息
- 2.SecurityManager 安全管理器 官方文档
- 就如同名字一样,Shiro通过SecurityManager管理者所有的内部组件实例,并通过它提供各种服务。是一个典型的Facade模式,默认实现是DefaultSecurityManager
- 3.Realms 安全域 官方文档
- Realm 本质上一个安全相关的DAO,它封装了数据源的连接细节,并在需要时,将合适的信息提供给Shiro使用,可以存在多个Realm,但是最少需要一个
- Shiro 提供了几种基础的Realm
- 一般情况下会自定义Realm,因为需要整合实际的认证逻辑。通过继承
AuthorizingRealm
重写两个方法即可,分别是
- 认证 doGetAuthenticationInfo(AuthenticationToken token)
- 授权 doGetAuthorizationInfo(PrincipalCollection principals)
- 然后交由SecurityManager统一管理
- Shiro 支持权限URL权限过滤通过不同的Filter来做处理
- anon 匿名 /user/**=anon 没有任何权限过滤
AnonymousFilter
- authc 认证 /user/id=authc 必须通过认证才可以访问使用
AuthenticationFilter
- roles 角色 /user/**=roles[admin,...] 可以指定多个参数,通过逗号分割
AuthorizationFilter
- perms 权限 /user/update/id=perms[user:update:*],...可以指定多个参数,通过逗号分割
AuthorizationFilter
- authcBasic 基础认证 /user/**=authcBasic
AuthenticationFilter
- ssl 安全请求 /user/**=ssl 协议必须为https
AuthorizationFilter
如何组合Shiro与Jwt
- authc 认证的默认实现有两种
- 第一种是基础的Http认证
- 第二种是用户密码的认证方式
- Shiro管理权限与认证,Jwt负责作为令牌记录信息,这种模式显然原生的Shiro是无法支持的。所以需要对Shiro的authc的filter做自定义。
- 需要注意的是对应的每个权限认证都是独立的filter完成,比如定义了/xxx/**=anon,自定的filter继承了
AuthenticatingFilter
,这是不会进入自定的filter,只会进入AnonymousFilter
的子类。因为在PathMatchingFilter中对路径进行了匹配,调用对应的filter
- 整合Shrio+Jwt 需要自定义filter 与 Realm
- 自定义filter 来判断哪些哪些请求
- 自定已Realm 进行具体的认证与授权
/**
* 创建token
*
* @param request 请求
* @param response 响应
* @return {@link ThunderToken}
*/
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
HttpServletRequest servletRequest = (HttpServletRequest) request;
return new ThunderToken(servletRequest.getHeader(CommonConstants.AUTHORIZATION_SIGN));
}
/**
* 做令牌拦截
* 判断请求头是否包含令牌
*
* @param request 请求
* @param response 响应
* @param mappedValue mappedValue
* @return 通过/拒绝
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
// 生成令牌
ThunderToken token = (ThunderToken) createToken(request, response);
boolean isAccessAllowed = false;
// 判断令牌是否存在
if (token.getToken() != null && !token.getToken().isEmpty()) {
try {
// 如果存在交由Realm认证
getSubject(request, response).login(token);
// 没有异常表示通过认证,如果Realm有异常向上抛出由Filter统一处理,反馈友好的认证信息
isAccessAllowed = true;
} catch (TokenExpiredException e) {
expiredToken(response);
} catch (IllegalTokenException e) {
illegalToken(response);
} catch (Exception e) {
unknownError(response);
}
} else {
unAuthorized(response);
}
return isAccessAllowed;
}
/**
* 系统令牌
* 因为前后端分离,所以只需要记录TOKEN密钥即可
*/
public class ThunderToken implements AuthenticationToken {
private String token;
public ThunderToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
@Override
public String toString() {
return "ThunderToken{" +
"token='" + token + '\'' +
'}';
}
}
各种问题的解决办法
- 请求不进入filter
- 确认配置的是否是 authc 如果不是不会进入filter
- 服务之间调用令牌不会传递
- 如果使用的Feign需要自定义拦截器,获取到RequestTemplate对象自定义请求头