shiro结合springboot多登录方式在springcloud中的配置

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的方式呢,就可以根据自己相关的业务来进行了!分享就到这里了,如果有帮助的话,点个赞吧?

猜你喜欢

转载自blog.csdn.net/weixin_53690059/article/details/127953163