Shiro是大家使用来进行后台权限配置的一个框架,通过本文你可以轻松配置Shiro,并完成多Realm的接入,多种授权方式,并解决很多Shiro框架本身带来的隐藏问题。
我相信大家看完这篇文章,都可以很快精通Shiro并进行配置操作。
引入基本的框架包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
首先定义错误的依赖项,未授权,未鉴权这两个状态需要进行区分
/**
* 授权异常定义.
*
* @author ucucs.
*/
public enum AuthErrorType implements ErrorType {
AUTH_ERROR("4400", "授权出现异常"),
/** 未授权错误. */
UNAUTHORIZED("4401", "未授权错误"),
/** 未登录错误. */
UNAUTHENTICATED("4402", "未登录错误");
/** 错误代码. */
private final String code;
/** 错误提示. */
private final String msg;
AuthErrorType(String code, String msg) {
this.code = code;
this.msg = msg;
}
@Override
public String getCode() {
return code;
}
@Override
public String getMsg() {
return msg;
}
}
需要定义通用的Filter,进行统一的鉴权管理
public class ShiroAuthorizationFilter extends AuthorizationFilter {
protected Function<Subject, Boolean> checkExtraAccessFunc = null;
public ShiroAuthorizationFilter() {
this.checkExtraAccessFunc = subject -> subject.hasRole(RoleType.SUPER_ADMIN);
}
public ShiroAuthorizationFilter(Function<Subject, Boolean> extraAccessFunc) {
this.checkExtraAccessFunc = extraAccessFunc;
}
@Override
protected boolean isAccessAllowed(
ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = getSubject(request, response);
HttpServletRequest servletRequest = (HttpServletRequest) request;
String servletPath = servletRequest.getServletPath();
if (!subject.isAuthenticated()) {
subject.logout();
return false;
}
if (checkExtraAccessFunc != null) {
boolean extraAccess = checkExtraAccessFunc.apply(subject);
if (extraAccess) {
return true;
}
}
return subject.isPermitted(servletPath);
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response)
throws IOException {
// 重写该方法主要是避免302跳转
Subject subject = getSubject(request, response);
HttpServletResponse resp = (HttpServletResponse) response;
resp.setCharacterEncoding(StandardCharsets.UTF_8.name());
resp.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
ErrorType errorType =
subject.isAuthenticated() ? AuthErrorType.UNAUTHORIZED : AuthErrorType.UNAUTHENTICATED;
Result<?> message = Result.fail(errorType);
resp.getWriter().write(JsonUtil.toJson(message));
return false;
}
}
多Realm的情况下,Shiro自身的框架判断,是需要两个Realm都鉴权成功,满足资源配置要求,才代表通过。
但很多时候,不同的登录方式,可能要求的是独立的鉴权模式,所以我们需要重写Shiro多Realm下的鉴权判断方法,只要有一个成功即可
public class ShiroModularRealmAuthorizer extends ModularRealmAuthorizer {
public ShiroModularRealmAuthorizer() {}
public ShiroModularRealmAuthorizer(Collection<Realm> realms) {
super(realms);
}
@Override
public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
for (Realm realm : this.getRealms()) {
if ((realm instanceof Authorizer)
&& ((Authorizer) realm).hasRole(principals, roleIdentifier)) {
return true;
}
}
return false;
}
@Override
public boolean isPermitted(PrincipalCollection principals, String permission) {
for (Realm realm : this.getRealms()) {
if ((realm instanceof Authorizer)
&& ((Authorizer) realm).isPermitted(principals, permission)) {
return true;
}
}
return false;
}
}
重新定义一个通用的Shiro资源加载的接口,方便额外实现加载角色和权限
public interface ShiroPermissionService {
Set<String> getPermissionsByRole(String corpId, String userId);
Set<String> getRoleByUserName(String corpId, String userId);
}
最后,肯定离不开缓存,缓存是可以自定义的,我们采用Redis
public class ShiroRedisCache<K, V> implements Cache<K, V> {
private static final Logger logger = LoggerFactory.getLogger(ShiroRedisCache.class);
protected final RedisTemplate<String, Object> redisTemplate;
protected final String keyPrefix;
protected final int expireSecond;
public ShiroRedisCache(
RedisTemplate<String, Object> redisTemplate, String prefix, int expireSecond) {
this.redisTemplate = redisTemplate;
this.keyPrefix = prefix;
this.expireSecond = expireSecond;
}
/**
* get shiro authorization redis key-value
*
* @param key key
* @return value
* @throws CacheException get cache exception
*/
@SuppressWarnings("unchecked")
@Override
public V get(K key) throws CacheException {
if (key == null) {
return nul