spring+shiro 配置使用(完整代码篇)

7 篇文章 0 订阅
2 篇文章 0 订阅

web安全框架,shiro相比spring security,更轻量级,配置简单易懂,小巧灵活,功能强大,和spring完美结合,shiro上手超级简单,一看就懂,但如果需求较为复杂,仍需要细细研究其中原理,灵活配置。本人才疏学浅,本文仅涉及登录验证,动态权限验证,后面有机会再慢慢研究。


引入Shiro的Maven依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.3.0</version>
</dependency>

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>1.3.0</version>
</dependency>

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.0</version>
</dependency>

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-quartz</artifactId>
    <version>1.3.0</version>
</dependency>

web.xml配置

<!-- 配置shiro的核心拦截器 -->
<filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

spring-shiro.xml 配置

<!-- 配置权限管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">

        <!-- ref对应我们写的realm JuJinShiro -->
        <property name="realm" ref="JuJinShiro"/>

        <!-- 使用下面配置的缓存管理器 -->
        <property name="cacheManager" ref="cacheManager" />

    </bean>

    <bean id="JuJinShiro" class="com.hd.security.JuJinShiro"></bean>

    <bean id="chainDefinitionSectionMetaSource" class="com.hd.security.ChainDefinitionSectionMetaSource">  

        <property name="filterChainDefinitions">
            <value>
                <!-- anon表示此地址不需要任何权限即可访问 -->
                /css/**=anon
                /imgs/**=anon
                /js/**=anon
                /auth/**=authc
            </value>
        </property>
    </bean>

    <!-- 配置shiro的过滤器工厂类,id- shiroFilter要和我们在web.xml中配置的过滤器一致 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">

        <!-- 调用我们配置的权限管理器 -->
        <property name="securityManager" ref="securityManager" />

        <!-- 配置我们的登录请求地址 -->
        <property name="loginUrl" value="/auth/silencelogin" />

        <!-- 配置我们在登录页登录成功后的跳转地址 -->
        <property name="successUrl" value="/auth/auth" /> 

        <!-- 如果您请求的资源不再您的权限范围,则跳转到无权限地址 -->
        <property name="unauthorizedUrl" value="/auth/unauthorized" />

        <!-- 权限配置,动态的可以读取数据库 -->
        <property name="filterChainDefinitionMap" ref="chainDefinitionSectionMetaSource" />     
    </bean>

    <!-- 会话管理器 -->
    <bean id="sessionManager"
        class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <property name="globalSessionTimeout" value="1800" />
        <property name="deleteInvalidSessions" value="true" />
        <property name="sessionValidationSchedulerEnabled" value="true" />
    </bean>

    <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

自定义realm配置

package com.hd.security;

import java.util.List;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.hd.common.Constants;
import com.hd.dao.mappers.security.PermissionDao;
import com.hd.entity.login.LoginBean;
import com.hd.service.login.LoginService;

@Service
@Transactional
public class JuJinShiro extends AuthorizingRealm{

    protected static Logger logger = LoggerFactory.getLogger(JuJinShiro.class);

    @Autowired
    private LoginService loginService;

    @Autowired
    private PermissionDao permissionDao;

    /**
     * 权限认证(仅使用了permission)
     */
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        //权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)  
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        String userId = (String) principalCollection.fromRealm(getName()).iterator().next();

        // 查询用户所对应的资源ID
        List<String> resources = permissionDao.getResourceForUser(userId);

        info.addStringPermissions(resources);

        return info;
    }

    /**
     * 登录认证
     */
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authenticationToken) throws AuthenticationException {

        //UsernamePasswordToken对象用来存放提交的登录信息  
        UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken; 

        char[] password = token.getPassword();

        //查询用户  
        LoginBean user = new LoginBean();
        try {
        // 验证登录信息
            user = loginService.loginCheck(token.getUsername(),String.valueOf(password));
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }  

        //注入session
        Session shiroSession = SecurityUtils.getSubject().getSession();
        shiroSession.setAttribute(Constants.USER_KEY, user);
        shiroSession.setAttribute(Constants.USERNAME_KEY, user.getUser_id());

        //将此用户存放到登录认证info中  
        return new SimpleAuthenticationInfo(user.getUser_id(), String.valueOf(password), getName());
    }  
}
package com.hd.security;

import org.apache.shiro.config.Ini;
import org.apache.shiro.config.Ini.Section;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;

import com.hd.service.security.PermissionService;

public class ChainDefinitionSectionMetaSource implements FactoryBean<Ini.Section>{

    @Autowired
    private PermissionService permissionService;

    public static String filterChainDefinitions;

    /** 
     * 默认permission字符串 
     */  
    public static final String PREMISSION_STRING ="perms[\"{0}\"]";  

    @Override
    public Section getObject() throws Exception {
        // 初始化权限
        return permissionService.initPermission(filterChainDefinitions);
    }

    /** 
     * 通过filterChainDefinitions对默认的url过滤定义 
     *  
     * @param filterChainDefinitions 默认的url过滤定义 
     */  
    public void setFilterChainDefinitions(String filterChainDefinitions) {  
        this.filterChainDefinitions = filterChainDefinitions;  
    }  

    @Override
    public Class<?> getObjectType() {
        // TODO Auto-generated method stub
        return this.getClass();
    }

    @Override
    public boolean isSingleton() {
        // TODO Auto-generated method stub
        return false;
    }


}

权限操作

package com.hd.service.security;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.shiro.config.Ini;
import org.apache.shiro.config.Ini.Section;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.hd.dao.mappers.security.PermissionDao;
import com.hd.service.BaseService;
import com.hd.utils.ShiroUtils;

@Service
public class PermissionService extends BaseService{

    @Autowired  
    private ShiroFilterFactoryBean shiroFilterFactoryBean; 

    @Autowired
    private PermissionDao permissionDao;

    /** 
     * 默认permission字符串 
     */  
    public static final String PREMISSION_STRING="perms[\"{0}\"]";  

    protected static final Logger logger = LoggerFactory.getLogger(PermissionService.class);


    /**
     * 初始化框架权限资源配置   
     */
    public Section initPermission(String filterChainDefinitions) {
        //shiroFilterFactoryBean.setFilterChainDefinitionMap(obtainPermission(filterChainDefinitions));  

        Section section = obtainPermission(filterChainDefinitions);

        logger.info("初始化shiro权限成功...");
        return section;
    }

    /**
     * 更新权限资源配置 (强制线程同步)
     */
    public void updatePermission(String filterChainDefinitions) {

         synchronized (shiroFilterFactoryBean) { 

            AbstractShiroFilter shiroFilter = null;  

            try {  
                shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean.getObject();  
            } catch (Exception e) {  
                logger.error(e.getMessage());  
            }  

            // 获取过滤管理器  
            PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter  
                    .getFilterChainResolver();  
            DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();  

            // 清空初始权限配置  
            manager.getFilterChains().clear();  
            shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();  

            // 重新构建生成  
            shiroFilterFactoryBean.setFilterChainDefinitions(filterChainDefinitions);  
            Map<String, String> chains = obtainPermission(filterChainDefinitions);  

            for (Map.Entry<String, String> entry : chains.entrySet()) {  
                String url = entry.getKey();  
                String chainDefinition = entry.getValue().trim().replace(" ", "");  
                manager.createChain(url, chainDefinition);  
            }  

            //清除用户授权信息
            ShiroUtils.clearAllCachedAuthorizationInfo();

            logger.info("更新shiro权限成功...");  
        }  
    }



    /** 
     * 读取配置资源和第三方资源
     *  
     */  
    private Section obtainPermission(String filterChainDefinitions) {  
        Ini ini = new Ini();  
        ini.load(filterChainDefinitions); // 加载资源文件节点串  
        Section section = ini.getSection(Ini.DEFAULT_SECTION_NAME); // 使用默认节点  

        Map<String, String> permissionMap = initOtherPermission();  
        if (permissionMap != null && !permissionMap.isEmpty()) {  
           section.putAll(permissionMap);  
        } 

        return section;  
    }


    /**
     * 读取第三方资源
     * @return
     */
    public Map<String, String> initOtherPermission() {

        Map<String,String> permissionMap = new HashMap<>();

        // 获取所有需要权限的接口资源
        List<Map<String, Object>> resources = permissionDao.getAllResources();

        for (Map<String,Object> resource : resources) {
            String code = resource.get("code").toString();
            String url = resource.get("url").toString();

            permissionMap.put("/**/"+url+"/**", MessageFormat.format(PREMISSION_STRING,code));
        }
        //permissionMap.put("/**/auth/**", "authc");


        return permissionMap;
    }
}

登录controller

package com.hd.controller.login;

import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.util.SavedRequest;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.hd.controller.BaseController;
import com.hd.controller.MakeVerifyCodeService;
import com.hd.entity.login.LoginBean;
import com.hd.redis.RedisUtil;
import com.hd.security.ChainDefinitionSectionMetaSource;
import com.hd.service.login.LoginService;
import com.hd.service.security.PermissionService;
import com.hd.utils.CookieTool;
import com.jujin.common.ExceptionHelper;
import com.jujin.common.OpResult;

@Controller
@RequestMapping(value = "")
public class loginControler extends BaseController{

    protected static final Logger logger = LoggerFactory.getLogger(loginControler.class);
    String verifyCode = "verifyCode";
    @Autowired
    private PermissionService permissionService;
    @Autowired
    private LoginService loginService;

    /**
     * 本系统用户登陆
     * @param userid
     * @param password
     * @return
     */
    @RequestMapping(value = "/login",method = RequestMethod.POST)
    @ResponseBody
    public OpResult login(HttpEntity<LoginBean> model,HttpServletRequest request, HttpServletResponse response) {
        OpResult result = new OpResult();
        LoginBean login = model.getBody();
        String userId = login.getUser_id();
        String password = login.getPassword();
        if(StringUtils.isEmpty(userId) || StringUtils.isEmpty(password) ){
            result.setMsg("输入参数不完整");
            result.setStatus(false);
            return result;
        }           

        try {

            Subject subject = SecurityUtils.getSubject();       
            Session session = subject.getSession();
            String  code =  login.getCode();
            if ("1".equals(session.getAttribute("codeIsShow"))) {
                String sessionCode = (String) session.getAttribute(verifyCode);
                if(code == null){
                    result.setMsg("请输入验证码");
                    result.setStatus(false);
                    return result;
                }
                if (!StringUtils.isEmpty(code) && !code.equals(sessionCode)) {
                    result.setMsg("验证码错误,请重新输入");
                    result.setStatus(false);
                    return result;
                }       
            }
            UsernamePasswordToken token = new UsernamePasswordToken(userId, password);
            //登陆认证
            subject.login(token);           
            //登陆日志
            logger.info("用户" + userId + "在" + sysdate() + "登陆");            
            result.setStatus(true);
            result.setLoginstatus(1);

            // 更新用户权限
            permissionService.updatePermission(ChainDefinitionSectionMetaSource.filterChainDefinitions);
            logger.info("更新用户[" + this.getLoginUserId(request) + "]权限成功");          
        } catch (Exception e) {
            result.setStatus(false);
            result.setLoginstatus(0);
            result.setMsg(e.getCause().getMessage());
        }       
        if (supportJsonp(request)) {

            jsonpWrapper(request, response, result);

            return null;

        } else {

            return result;
        }

    }
     // 普通图片验证码
    @RequestMapping(value = "/verify", method = RequestMethod.GET)
    public @ResponseBody
    Object getVerifyImage(HttpServletRequest request,
            HttpServletResponse response) {

        try {
            response.setContentType("textml;charset=UTF-8");
            // 将请求、响应的编码均设置为UTF-8(防止中文乱码)
            request.setCharacterEncoding("UTF-8");

            response.setContentType("image/jpg");
            OutputStream os = response.getOutputStream();
            MakeVerifyCodeService service = MakeVerifyCodeService
                    .getVerifyCode(60, 20);
            String code = service.getCode();
            os.write(service.getImage());
            logger.info("生成的验证码:" + code);
            Session shiroSession = SecurityUtils.getSubject().getSession();
            if (shiroSession != null) {
                shiroSession.setAttribute(verifyCode, code);
            }
            String wx_token = request.getHeader("wx-token");
            if (!StringUtils.isEmpty(wx_token)) {
                RedisUtil.setString("VERIFY_" + wx_token, code, 600);
            }
            Cookie c = CookieTool.getCookieByName(request, "wx_token");
            if (c != null) {
                RedisUtil.setString("VERIFY_" + c.getValue(), code, 600);
            } else {
                String token = UUID.randomUUID().toString().toLowerCase()
                        .replace("-", "");
                RedisUtil.setString("VERIFY_" + token, code, 600);
                Cookie cookie = new Cookie("wx_token", token);
                cookie.setPath("/");
                response.addCookie(cookie);
            }
            os.flush();
            os.close();
        } catch (Exception e) {
            logger.error(ExceptionHelper.getExceptionDetail(e));
        }
        return null;
    }


    /**
     * 系统时间
     * @return
     */
    protected String sysdate() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
    }

    /**
     * 本系统用户注销
     * @return
     */
    @RequestMapping(value = "/logout")
    @ResponseBody
    public OpResult loginout(HttpServletRequest request, HttpServletResponse response) {

        OpResult result = new OpResult();

        try {
            Subject subject = SecurityUtils.getSubject();

            if (subject.isAuthenticated()) {
                logger.info("用户" + this.getLoginUserId(request) + "在" + this.sysdate() + "注销");
                subject.logout();
            }
            result.setStatus(true);
            result.setLoginstatus(0);
        } catch (Exception e) {
            result.setStatus(false);
            result.setMsg(e.getMessage());
        }


        return result;

    }


    /**
     *  
     * 
     * @param request
     */
    @RequestMapping(value = "/silencelogin")
    public String loginSilence(HttpServletRequest request) {

        Subject subject = SecurityUtils.getSubject();

        SavedRequest savedRequest = WebUtils.getSavedRequest(request);
        //获取原始请求URL
        String originalUrl = savedRequest.getRequestUrl();
        String contextPath = request.getContextPath();

        if (originalUrl.startsWith(contextPath)) {
            originalUrl = originalUrl.substring(contextPath.length(), originalUrl.length());
        }

        if (subject.isAuthenticated()) {

            return "forward:" + originalUrl;

        } else {
            //未登录,提示用户先登录
            if (originalUrl.contains("callback")) {

                originalUrl = originalUrl.replace(originalUrl.substring(0, originalUrl.indexOf("?")), "/needlogin"); 

                return "forward:" + originalUrl;
            } else {

                return "forward:/needlogin";
            }

        }


    }

    /**
     * 提示用户需要登录
     * @return
     */
    @RequestMapping(value = "/needlogin")
    @ResponseBody
    public OpResult needLogin(HttpServletRequest request, HttpServletResponse response) {
        OpResult op = new OpResult();

        op.setStatus(true);
        op.setMsg("请登录");
        op.setLoginstatus(0);

        if (supportJsonp(request)) {

            jsonpWrapper(request, response, op);

            return null;

        } else {

            return op;
        }
    }

    /**
     * 提示用户需要权限
     * @return
     */
    @RequestMapping(value = "/unauthorized")
    @ResponseBody
    public OpResult unauthorized(HttpServletRequest request, HttpServletResponse response) {
        OpResult op = new OpResult();

        op.setStatus(false);
        op.setMsg("没有权限访问");
        op.setLoginstatus(1);

        if (supportJsonp(request)) {

            jsonpWrapper(request, response, op);

            return null;

        } else {

            return op;
        }
    }
}

登录认证业务例子:

  • 比如我们配置了/auth/**=authc,表示所有带/auth的资源都会被拦截进行登录验证,现在zhangsan未登录首次访问资源/auth/test,shiro拦截到以后会跳转到我们配置的<property name="loginUrl" value="/silencelogin" />,具体看loginController。
  • zhangsan登录时,验证信息(自己写service判断),登录成功后,shiro会将登录信息存放到认证信息类中,并缓存起来,再次访问/auth/test时,shiro就不会拦截,直接访问资源。

注意:缓存的前提是,我们配置了
<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />

权限认证业务例子:

比如用户shangsan,拥有角色管理员 1,管理员对应的权限为a,b,资源(接口)/test1 对应的权限为a,/test2对应的权限为c
  • 项目启动时会初始化资源权限信息并缓存起来, 比如/test1 对应a, /test2对应c
  • zhangsan登录时会查询他对应的权限,并缓存起来,比如 zhangsan对应a,b
  • 当zhangsan访问/test1资源时,shiro会把前面两个存储的权限(a),(a,b)进行对比,zhangsan有a这个权限,就可以访问。
  • 当shangsan访问/test2资源时,shiro会把前面两个存储的权限(c),(a,b)进行对比,zhangsan没有c这个权限,就不能访问,跳转到spring-shiro配置的<property name="unauthorizedUrl" value="/unauthorized" />

注意:权限认证之前会先进行登录认证,就算/test1不是/auth/test1,也会进行登录验证

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值