springcloud结合shiro配置内容文章
提示:思路和配置仅供参考
文章目录
前言
上一篇文章介绍了我在使用springcloud时如何结合上shiro等安全工具整合了一个属于自己的使用思路,然后因为有些同学私信我需要结合配置使用,那么今天刚好周末有空,乘此机会将自己的配置以及思路分享出来。
提示:思路与配置仅供参考,详细请按照自己的项目决定配置。
一、Shiro配置问题?
那么使用shiro之前,希望大家都有具体的shiro使用经验,或者了解shiro,在这里我使用的是shiro+jwt的token配置,那么shiro其实是基于session的将验证后的数据存在全局的代码中的,使用jwt生产token的话,token是无状态的并且不存在于本地内存中。
那么废话不多说,上代码:
maven配置
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
全局shiro配置
package com.mrone.config;
import com.mrone.filter.ShiroFilter;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SubjectFactory;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.*;
@Configuration
public class ShiroConfig {
/**
* 告诉shiro不创建内置的session
*/
@Bean
public SubjectFactory subjectFactory() {
return new ShiroDefaultSubjectFactory();
}
//shiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean shiroFilterBean(@Qualifier("Manager") DefaultSecurityManager defaultSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultSecurityManager);
/**
* 添加内置shiro过滤器
* anno 无需认证可访问
* authc认证了才能访问
* user 必须拥有记住我功能 才能使用
* perms 拥有对某个资源的权限才能访问
* role 拥有某个角色权限才能访问
*/
// 注册jwt过滤器,也就是将jwtFilter注册到shiro的Filter中,并在下面注册,指定除了login和index之外的请求都先经过jwtFilter
Map<String, Filter> filterMap = new HashMap<String, Filter>(3) {
{
put("anon", new AnonymousFilter());
put("jwt", new ShiroFilter());
put("logout", new LogoutFilter());
}
};
bean.setFilters(filterMap);
//设置拦截器
Map<String, String> filterRuleMap = new LinkedHashMap<String, String>() {
{
//登录注册主页放行
put("/login", "anon");
put("/register", "anon");
put("/","anon");
put("/index","anon");
put("/wx/**","anon");
put("/user/login","anon");
put("/admin/login","anon");
// swagger放行
put("/swagger-ui.html", "anon");
put("/swagger-resources", "anon");
put("/v2/api-docs", "anon");
put("/webjars/springfox-swagger-ui/**", "anon");
put("/configuration/security", "anon");
put("/configuration/ui", "anon");
put("/feign/**","anon");
// 任何请求都需要经过jwt过滤器
put("/**", "jwt");
}
};
bean.setFilterChainDefinitionMap(filterRuleMap);
return bean;
}
/**
* 创建安全管理器
*/
@Bean("Manager")
public DefaultWebSecurityManager getManager(UserRealm userRealm, AdminRealm adminRealm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
List<Realm> list = new ArrayList<>();
// 使用自己的realm
list.add(userRealm);
list.add(adminRealm);
manager.setRealms(list);
// 关闭shiro自带的session
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
manager.setSubjectDAO(subjectDAO);
return manager;
}
/**
* 系统自带的Realm管理,主要针对多realm
* */
@Bean
public ModularRealmAuthenticator modularRealmAuthenticator(){
//自己重写的ModularRealmAuthenticator
UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());//这里为默认策略:如果有一个或多个Realm验证成功,所有的尝试都被认为是成功的,如果没有一个验证成功,则该次尝试失败
return modularRealmAuthenticator;
}
//开启Shiro注解支持
/**
* 如果userPrefix和proxyTargetClass都为false会导致 aop和shiro权限注解不兼容 资源报错404
* 因此两个属性至少需要其中一个属性为true才可以
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
* 开启aop注解支持
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("Manager") DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); // 这里需要注入 SecurityManger 安全管理器
return authorizationAttributeSourceAdvisor;
}
}
关闭shiro的session处理
package com.mrone.config;
import org.apache.shiro.mgt.DefaultSubjectFactory;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
/**
* Created 一字先生
*
* @Author: Mr.One
* @Date: 2022/06/14 14:34
* @Description: 使用token验证关闭shiro session
*/
public class ShiroDefaultSubjectFactory extends DefaultSubjectFactory {
@Override
public Subject createSubject(SubjectContext context) {
// 不创建shiro内部的session
context.setSessionCreationEnabled(false);
return super.createSubject(context);
}
}
因为我是两种登录方式,所以我有两种处理登录方式的realm,但是功能上是一样的,这里就放一个在这
package com.mrone.config;
import com.mrone.dto.AdminDto;
import com.mrone.feignclients.AdminClient;
import com.mrone.util.JwtUtil;
import com.mrone.util.RedisUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Created 一字先生
*
* @Author: Mr.One
* @Date: 2022/06/24 10:46
* @Description:
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class AdminRealm extends AuthorizingRealm {
@Autowired
private JwtUtil jwtConfig;
@Autowired
private RedisUtil redisUtill;
@Autowired
private AdminClient adminClient;
@Override
public String getName() {
return "admin";
}
/**
* 多重写一个support
* 标识这个Realm是专门用来验证JwtToken
* 不负责验证其他的token(UsernamePasswordToken)
*/
@Override
public boolean supports(AuthenticationToken token) {
// 这个token就是从过滤器中传入的jwtToken
return token instanceof JwtToken;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String phone = (String) principalCollection.getPrimaryPrincipal();
if(phone==null){
return null;
}
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
AdminDto admin = adminClient.feignByPhone(phone);
if(admin==null){
return null;
}
if(admin.getRole()==null&&admin.getPermission()==null||admin.getRole().equals("")&&admin.getPermission().equals("")){
//默认admin用户权限
authorizationInfo.addRole("admin");
authorizationInfo.addStringPermission("admin");
}else {
//数据库获取角色权限 授权
authorizationInfo.addRole(admin.getRole());
authorizationInfo.addStringPermission(admin.getPermission());
}
return authorizationInfo;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("进行管理员用户认证");
String token = (String) authenticationToken.getPrincipal();
//解码token admin就是传入的phone
String phone = jwtConfig.decodeToken2(token);
if (phone == null || token == null) {
throw new IncorrectCredentialsException("Authorization token is invalid");//抛出异常 没有token
}
//与redis里的数据进行比较 看是否相等
Object user = redisUtill.get(phone);
if (user != null) {
// claims放入全局Subject中
return new SimpleAuthenticationInfo(phone, token, "AdminRealm");
}
return null;
}
}
根据不同的处理器来校验是哪种登陆方式最后去到相应的realm处理
package com.mrone.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;
import java.util.ArrayList;
import java.util.Collection;
/**
* @program: shiro
* @description: 多realm处理
* @author: Mr.One
* @create: 2022-06-24 17:06
**/
@Slf4j
public class UserModularRealmAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
//强制转换用户token
JwtToken jwtToken = (JwtToken) authenticationToken;
Collection<Realm> realms = getRealms();
String loginType = jwtToken.getLoginType();
Collection<Realm> typeRealms = new ArrayList<>();
Realm realm = null;
for (Realm realmItem : realms) {
log.info("Realm>>>>{}", realmItem.getName());
log.info("Realm>>>>{}", loginType);
if (realmItem.getName().contains(loginType)) {
log.info("Realm>>{}", "user请求");
realm = realmItem;
break;
}
if (realmItem.getName().contains(loginType)) {
log.info("Realm>>{}", "admin请求");
realm = realmItem;
break;
}
}
if (typeRealms.size() == 1) {
return doSingleRealmAuthentication(typeRealms.iterator().next(), jwtToken);
} else {
return doMultiRealmAuthentication((Collection<Realm>) realm, jwtToken);
}
}
}
总结
提示:我是将配置类全部放入公共的微服务中,不仅仅是config配置,uitl以及maven依赖,在其他微服务引入公共微服务时,公共微服务所有的内容都可以随其他微服务使用,可以简单理解为一个maven依赖:
当然jwt的配置以及创建jwt的方式呢,就可以根据自己相关的业务来进行了!分享就到这里了,如果有帮助的话,点个赞吧?