美文网首页
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