美文网首页
spring-boot集成shiro框架

spring-boot集成shiro框架

作者: java_飞 | 来源:发表于2019-04-17 11:38 被阅读0次

情景:这是个后台系统之前是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.请求规则的添加,注意规则是从上到下匹配的,如果之前匹配到了的规则就不会走下面的匹配规则,意味着走拦截器的顺序就会不一样。

相关文章

网友评论

      本文标题:spring-boot集成shiro框架

      本文链接:https://www.haomeiwen.com/subject/avugwqtx.html