spring-boot集成shiro框架

情景:这是个后台系统之前是MVC项目这次也是要改造为boot,这里涉及到了shiro框架的集成,幸运的是我之前有独自用boot集成shiro过的小项目,所以差不多这里就是依样画葫芦,难度不是很大,但是做完后,交给同事,同事发现请求相关接口时,即使没有登录也可以访问接口,那么问题来了,这是为什么呢?

解决:首先来说说怎么集成shiro;
1.pom依赖,版本自行选择

   <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-web -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
        </dependency>

2.自定义的密码验证器,这个类的作用是自己设置密码验证策略

package com.rt.platform.admin.common.shiro.credentials;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {

    private static ConcurrentHashMap<String, Entry> passwordRetryCache = new ConcurrentHashMap<String, Entry>();

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        String username = (String)token.getPrincipal();
        //retry count + 1
        Entry entry = passwordRetryCache.get(username);
        if(entry == null) {
            entry = new Entry(TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis()), new AtomicInteger(0));
            passwordRetryCache.put(username, entry);
        }
        //timeunit
        if(TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis()) - entry.lastTime > 10){//TODO 改成配置项
            passwordRetryCache.remove(username);
        }else if(entry.count.incrementAndGet() > 5) {
            throw new ExcessiveAttemptsException();
        }

        boolean matches = super.doCredentialsMatch(token, info);
        if(matches) {
            //clear retry count
            passwordRetryCache.remove(username);
        }
        
        return matches;
    }
    
    private static class Entry{
        
        private Entry(long lastTime, AtomicInteger count){
            this.count = count;
            this.lastTime = lastTime;
        }
        
        private AtomicInteger count;
        
        private long lastTime;
        
    }
}

3.自定义表单提交拦截器,作用是拦截表单提交请求,主要就是登录的请求;

package com.rt.platform.admin.common.shiro.filter;

import com.rt.platform.admin.common.shiro.user.dto.AdminUserDto;
import com.rt.platform.admin.common.shiro.user.service.IAdminUserService;
import com.rt.platform.admin.common.shiro.user.vo.AdminUserVo;
import com.rt.platform.infosys.base.common.utils.NetworkUtil;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;

public class AdminFormAuthenticationFilter extends FormAuthenticationFilter{

    private Logger LOG = LoggerFactory.getLogger(AdminFormAuthenticationFilter.class);
    
    @Resource
    private IAdminUserService iAdminUserService;
    
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
            ServletResponse response) throws Exception {
        AdminUserDto user = iAdminUserService.getAdminUserByLoginName((String)token.getPrincipal());
        LOG.info("login success!login user id is:{},user name is:{}",user.getId(),user.getLoginName());
        AdminUserVo vo = new AdminUserVo();
        vo.setId(user.getId());
        vo.setLoginTime(new Date());
        vo.setLoginIp(NetworkUtil.getClientIP((HttpServletRequest)request));
        iAdminUserService.updateAdminUser(vo, null, false);
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        httpRequest.getRequestDispatcher(this.getSuccessUrl()).forward(httpRequest, response);
        return true;
    } 
    
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request,
            ServletResponse response) {
        return super.onLoginFailure(token, e, request, response);
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        return super.onAccessDenied(request, response);
    }
    
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue)
            throws Exception {
        return super.onAccessDenied(request, response, mappedValue);
    }


}

4.自定义userFilter,作用是类似记住密码的功能;

package com.rt.platform.admin.common.shiro.filter;

import org.apache.shiro.web.filter.authc.UserFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;


public class ShiroUserFilter extends UserFilter{

    public static final Logger LOG = LoggerFactory.getLogger(ShiroUserFilter.class);

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        LOG.info("====================shiro user filter!");
    //自定义相关操作
        return true;
 
    }
}

5.自定义shiro数据源,作用是对一些shiro相关权限的配置,例如,角色的permission等

package com.rt.platform.admin.common.shiro;

import com.rt.platform.admin.common.constants.AdminGlobalConstants;
import com.rt.platform.admin.common.shiro.user.dto.AdminPermissionDto;
import com.rt.platform.admin.common.shiro.user.dto.AdminUserDto;
import com.rt.platform.admin.common.shiro.user.dto.AdminUserRoleDto;
import com.rt.platform.admin.common.shiro.user.service.IAdminPermissionService;
import com.rt.platform.admin.common.shiro.user.service.IAdminUserService;
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.apache.shiro.util.ByteSource;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
public class AdminRealm extends AuthorizingRealm {

    @Resource
    private IAdminUserService iAdminUserService;

    @Resource
    private IAdminPermissionService iAdminPermissionService;

    @Override
    public String getName() {
        return "AuthorizingRealm";
    }

    // 支持UsernamePasswordToken
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        String loginName = (String) principals.getPrimaryPrincipal();
        AdminUserDto user = iAdminUserService.getAdminUserByLoginName(loginName);

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // 根据用户查询对应的角色
        List<AdminUserRoleDto> roleList = iAdminUserService.getAdminUserRoleByUserId(user.getId());
        // 组装角色集合
        Set<String> roles = new HashSet<String>();
        List<Long> roleIds = new ArrayList<Long>();
        if (roleList != null && roleList.size() > 0) {
            for (AdminUserRoleDto userRole : roleList) {
                if (userRole == null) {
                    continue;
                }
                roleIds.add(userRole.getRoleId());
                roles.add(userRole.getRoleId().toString());
            }
        }

        // 设置角色
        authorizationInfo.setRoles(roles);

        // 根据角色查询权限
        List<AdminPermissionDto> permissionList = iAdminPermissionService.getPermissionByRoleId(roleIds);

        Set<String> permissions = permissionList.stream().filter(dto -> dto != null)
                .map(AdminPermissionDto::getPermissionKey).collect(Collectors.toSet());
        // 设置权限
        authorizationInfo.setStringPermissions(permissions);
        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        if(Objects.isNull(token.getPrincipal())){
            return null;
        }
        String loginName =  token.getPrincipal().toString();

        AdminUserDto user = iAdminUserService.getAdminUserByLoginName(loginName);

        if (user == null) {
            throw new UnknownAccountException();// 没找到帐号
        }

        if (AdminGlobalConstants.INVALID == user.getIsValid()) {
            throw new LockedAccountException(); // 帐号锁定
        }

        // 交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getLoginName(), // 用户名
                user.getPassword(), // 密码
                ByteSource.Util.bytes(user.getLoginName() + AdminGlobalConstants.SALT), // salt=loginName+salt
                getName() // realm name
        );
        return authenticationInfo;
    }
}

6.重点来是接下去的这个类,用boot配置bean的方式配置原来MVC项目中xml的相关配置,如下类

package com.rt.platform.admin.common.shiro;

import com.rt.platform.admin.common.shiro.credentials.RetryLimitHashedCredentialsMatcher;
import com.rt.platform.admin.common.shiro.filter.AdminFormAuthenticationFilter;
import com.rt.platform.admin.common.shiro.filter.ShiroUserFilter;
import com.rt.platform.admin.common.shiro.filter.SysUserFilter;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.ServletContainerSessionManager;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.LinkedHashMap;

@Configuration
public class ShiroConfiguration {

//shiro数据源的bean
    @Bean
    public AdminRealm adminRealm(RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher) {
        AdminRealm adminRealm = new AdminRealm();
        adminRealm.setCredentialsMatcher(retryLimitHashedCredentialsMatcher);
        adminRealm.setCachingEnabled(false);
        return adminRealm;
    }

//安全管理器bean
    @Bean
    public SecurityManager securityManager(AdminRealm adminRealm,CookieRememberMeManager cookieRememberMeManager,
                                           ServletContainerSessionManager sessionManager
                                          ) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(adminRealm);
        securityManager.setRememberMeManager(cookieRememberMeManager);
        securityManager.setSessionManager(sessionManager);
        return securityManager;
    }


    @Bean
    public MethodInvokingFactoryBean methodInvokingFactoryBean(SecurityManager securityManager) {
        MethodInvokingFactoryBean methodInvokingFactoryBean=new MethodInvokingFactoryBean();
        Object[] objects=new Object[]{securityManager};
        methodInvokingFactoryBean.setArguments(objects);
        methodInvokingFactoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
        return methodInvokingFactoryBean;
    }


//主要关注这个别的基本可以忽略
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager, AdminFormAuthenticationFilter adminFormAuthenticationFilter,
                                                         ShiroUserFilter shiroUserFilter,SysUserFilter sysUserFilter) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
        LinkedHashMap<String, Filter> map2 = new LinkedHashMap<String, Filter>();
        map2.put("adminFormAuthenticationFilter",adminFormAuthenticationFilter);
        map2.put("shiroUserFilter",shiroUserFilter);
        map2.put("sysUserFilter",sysUserFilter);
        shiroFilterFactoryBean.setFilters(map2);
        //登录
        shiroFilterFactoryBean.setLoginUrl("/login.html");
        shiroFilterFactoryBean.setSuccessUrl("/index.html");
        //错误页面,认证不通过跳转
        shiroFilterFactoryBean.setUnauthorizedUrl("/404.html");
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //对所有用户认证
        map.put("/admin/login", "adminFormAuthenticationFilter");
        //登出
        map.put("/logout", "logout");
        map.put(" /**/*.html", "anon");
        map.put("/**/*.js", "anon");
        map.put("/webjar/**", "anon");
        map.put("/**", "sysUserFilter,shiroUserFilter,authc");//如果将authc改为adminFormAuthenticationFilter会报请求方式错误,如果不加authc会出现,任何请求都可以匿名访问
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }



//开启注解配置
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }


    @Bean
    public SimpleCookie simpleCookie() {
        SimpleCookie simpleCookie=new SimpleCookie("rememberMe");
        simpleCookie.setHttpOnly(true);
        simpleCookie.setMaxAge(2592000);
        return simpleCookie;
    }


    @Bean
    public ServletContainerSessionManager sessionManager() {
        return new ServletContainerSessionManager();
    }

    @Bean
    public CookieRememberMeManager cookieRememberMeManager(SimpleCookie simpleCookie) {
        CookieRememberMeManager cookieRememberMeManager=new CookieRememberMeManager();
        cookieRememberMeManager.setCipherKey("#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}".getBytes());
        cookieRememberMeManager.setCookie(simpleCookie);
        return cookieRememberMeManager;
    }


//表单提交登录验证的bean
    @Bean
    public AdminFormAuthenticationFilter adminFormAuthenticationFilter(){
        AdminFormAuthenticationFilter adminFormAuthenticationFilter = new AdminFormAuthenticationFilter();
        adminFormAuthenticationFilter.setUsernameParam("username");
        adminFormAuthenticationFilter.setPasswordParam("password");
        adminFormAuthenticationFilter.setRememberMeParam("rememberMe");
        adminFormAuthenticationFilter.setLoginUrl("/admin/login");
        adminFormAuthenticationFilter.setSuccessUrl("/admin/loginSuccess");
        return adminFormAuthenticationFilter;
    }

    @Bean
    public ShiroUserFilter shiroUserFilter(){
        return new ShiroUserFilter();
    }

    @Bean
    public SysUserFilter sysUserFilter(){
        return new SysUserFilter();
    }
//密码策略的bean
    @Bean
    public RetryLimitHashedCredentialsMatcher  retryLimitHashedCredentialsMatcher(){
        RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher = new RetryLimitHashedCredentialsMatcher();
        retryLimitHashedCredentialsMatcher.setHashAlgorithmName("md5");
        retryLimitHashedCredentialsMatcher.setHashIterations(2);
        retryLimitHashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return retryLimitHashedCredentialsMatcher;
    }


}

相关集成基本到这里结束了,注意点有

1.ShiroFilterFactoryBean 的配置,如果不配置authc这个默认的过滤器的话,就不能拦截所有需要权限的请求,因为我后台配置的登录接口,是post所以假如我将authc改为自定义的adminFormAuthenticationFilter,然后我用get方式去请求其他接口时,会报请求方式错误。

2.请求规则的添加,注意规则是从上到下匹配的,如果之前匹配到了的规则就不会走下面的匹配规则,意味着走拦截器的顺序就会不一样。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容