springboot整合shiro

1. pom.xml文件引入依赖

<properties>
    <shiro.version>1.3.2</shiro.version>
</properties>

<!-- 集成shiro -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>${shiro.version}</version>
</dependency>
<!-- shiro缓存 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>${shiro.version}</version>
</dependency>

2. 创建shiro配置类

import com.qfedu.rongzaiboot.shiro.UserRealm;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * shiro配置
 */
@Configuration
public class ShiroConfig {

    /**
     * session管理器
     * @return
     */
    @Bean(name = "sessionManager")
    public SessionManager sessionManager(){
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        //设置session的过期时间为1小时,(默认时间时30分钟)
        sessionManager.setGlobalSessionTimeout(60*60*1000);
        //开启扫描session线程,清理超时会话
        sessionManager.setSessionValidationSchedulerEnabled(true);
        //禁用了url重写 去掉URL中的JSESSIONID
        sessionManager.setSessionIdUrlRewritingEnabled(false);//默认true
        return sessionManager;
    }


    /**
     * 创建SecurityManager
     */
    @Bean
    public SecurityManager securityManager(UserRealm userRealm, SessionManager sessionManager){
        //密码加密规则
        /*HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("md5");
        credentialsMatcher.setHashIterations(1024);
        //credentialsMatcher.setHashSalted(true);
        userRealm.setCredentialsMatcher(credentialsMatcher);*/

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        securityManager.setSessionManager(sessionManager);
        securityManager.setCacheManager(ehCacheManager());
        return securityManager;
    }

    /**
     * 创建shiroFilter过滤器
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        //anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main -->
        //authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        shiroFilter.setLoginUrl("/login.html");//没有认证时跳转到的登陆页
        shiroFilter.setSuccessUrl("/index.html");//认证成功跳转到主页
        shiroFilter.setUnauthorizedUrl("/unauthorized.json");//未授权时的跳转链接

        Map<String,String> filterMap = new LinkedHashMap<>();
        filterMap.put("/public/**","anon"); //放行静态资源的路径
        filterMap.put("/login.html","anon");
        filterMap.put("/sys/login","anon");
        filterMap.put("/captcha.jpg","anon");//验证码的图片
        //filterMap.put("/**","authc");//authc经过认证才能访问

        //角色验证 具有admin角色的用户可以访问
        //filterMap.put("/sys/menu/del","roles[admin]");
        //权限验证 具有perms[sys:menu:update]可以访问
        //filterMap.put("/sys/menu/update","perms[sys:menu:update]");

        filterMap.put("/**","user");//通过记住我访问

        shiroFilter.setFilterChainDefinitionMap(filterMap);
        return shiroFilter;
    }

    /**
     * 创建shiro缓存
     * @return
     */
    @Bean
    public EhCacheManager ehCacheManager(){
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
        return ehCacheManager;
    }

    /**
     * ShiroConfig配置文件中开启注解
     * 配置三个bean:
     * lifecycleBeanPostProcessor
     * defaultAdvisorAutoProxyCreator
     * authorizationAttributeSourceAdvisor
     */

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
        proxyCreator.setProxyTargetClass(true);
        return proxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

}

2.1 创建自定义Realm类 实现认证和授权

package com.qfedu.rongzaiboot.shiro;

import com.qfedu.rongzaiboot.entity.SysUser;
import com.qfedu.rongzaiboot.service.SysUserService;
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;

import java.util.Set;

@Component
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private SysUserService sysUserService;

    /**
     * 认证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("认证........");

        String usernameInput = (String) token.getPrincipal();
        String passwordInput = new String((char[])token.getCredentials());
        //查询用户是否存在
        SysUser user = sysUserService.queryByUserName(usernameInput);
        if(user == null){
            throw new UnknownAccountException("账号或密码不正确");
        }

        //数据库中获取的用户名和密码
        String username = user.getUsername();
        String password = user.getPassword();

        //判断密码是否正确
        if(!passwordInput.equals(user.getPassword())){
            throw new IncorrectCredentialsException("账号或密码不正确");
        }

        //判断用户账号是否被锁定
        if (user.getStatus() == 0){
            throw new LockedAccountException("账号已被锁定,请联系管理员");
        }

        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, this.getName());

        return info;
    }

    /**
     * 授权
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("授权........");

        SysUser user = (SysUser) principals.getPrimaryPrincipal();
        Set<String> userPermissions = sysUserService.getUserPermissions(user.getUserId());

        SimpleAuthorizationInfo authorInfo = new SimpleAuthorizationInfo();
        authorInfo.addStringPermissions(userPermissions);

        //角色授权
        /*List<String> roleList = Arrays.asList("admin");
        authorInfo.addRoles(roleList);*/

        //资源授权
        /*List<String> permList = Arrays.asList("sys:menu:update");
        authorInfo.addStringPermissions(permList);*/

        return authorInfo;
    }
}

3. 创建service和dao

3.1 SysUserService接口和SysUserServiceImpl实现类

//接口
public interface SysUserService {

    /**
     * 根据用户名,查询系统用户
     */
    SysUser queryByUserName(String username);
}

//实现类
@Service
public class SysUserServiceImpl implements SysUserService {

    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private SysMenuMapper sysMenuMapper;

    @Override
    public SysUser queryByUserName(String username) {
        return sysUserMapper.queryByUserName(username);
    }

    @Override
    public Set<String> getUserPermissions(Long userId) {
        List<String> permsList = null;

        //超级管理员
        if (userId == 1) {
            List<SysMenu> menuList = sysMenuMapper.queryListAll();
            permsList = new ArrayList<>(menuList.size());
            for (SysMenu menu : menuList) {
                permsList.add(menu.getPerms());
            }
        }else {
            //普通用户授权
            permsList = sysUserMapper.queryAllPerms(userId);
        }

        Set<String> permsSet = new HashSet<>();
        for (String perms : permsList) {
            if (StringUtils.isBlank(perms)) {
                continue;
            }
            permsSet.addAll(Arrays.asList(perms.trim().split(",")));
        }
        return permsSet;
    }
}

3.2 mapper的sql语句

  <select id="queryByUserName" resultType="com.qfedu.rongzaiboot.entity.SysUser">
    select * from sys_user where username = #{username}
  </select>

    <select id="queryAllPerms" resultType="java.lang.String">
      SELECT perms FROM sys_menu m
        LEFT JOIN sys_role_menu rm ON m.menu_id = rm.menu_id
        LEFT JOIN sys_user_role ur ON ur.role_id = rm.role_id
        WHERE ur.user_id = #{userId}
    </select>

  <select id="queryListAll" resultType="com.qfedu.rongzaiboot.entity.SysMenu">
    select menu_id,name,url,perms,type,icon,order_num,(select p.name from sys_menu p where p.menu_id = m.parent_id) as parentName
    from sys_menu m order by m.order_num asc
  </select>

4. LoginController中添加登录controller方法

import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.Producer;
import com.qfedu.rongzaiboot.utils.R;
import com.qfedu.rongzaiboot.utils.ShiroUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Map;

@Controller
public class SysLoginController {

    @Autowired
    private Producer producer;

    /**
     * 生成验证码
     * @param response
     * @throws IOException
     */
    @RequestMapping("/captcha.jpg")
    public void kaptcha(HttpServletResponse response) throws IOException {
        //避免浏览器缓存
        response.setHeader("Cache-Control", "no-store,no-cache");
        response.setContentType("image/jpeg");

        //生成文字验证码
        String text = producer.createText();

        //生成图片验证码
        BufferedImage image = producer.createImage(text);

        //保存在session中
        ShiroUtils.setSessionAttribute(Constants.KAPTCHA_SESSION_KEY, text);

        //响应给客户端
        ServletOutputStream outputStream = response.getOutputStream();
        ImageIO.write(image, "jpg", outputStream);
        outputStream.flush();//清空缓冲区
    }

    /**
     * 登陆方法
     * @param map
     * @return
     */
    @ResponseBody
    @RequestMapping("/sys/login")
    public R login(@RequestBody Map<String,String> map) {
        String username = map.get("username");
        String password = map.get("password");
        String captcha = map.get("captcha");
        String rememberMe = map.get("rememberMe");

        String sessionCaptcha = ShiroUtils.getKaptcha(Constants.KAPTCHA_SESSION_KEY);
        if (!captcha.equalsIgnoreCase(sessionCaptcha)) {
            return R.error("验证码不正确");
        }

        boolean remember = false;
        if (rememberMe != null){
            remember = true;
        }

        Subject subject = ShiroUtils.getSubject();

        try {
            password = new Md5Hash(password,username,1024).toHex();
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);

            //设置记住我
            token.setRememberMe(remember);

            subject.login(token);
        }catch (UnknownAccountException e){
            return R.error(e.getMessage());
        }catch (IncorrectCredentialsException e){
            return R.error(e.getMessage());
        }catch (LockedAccountException e){
            return R.error(e.getMessage());
        }catch (AuthenticationException e){
            return R.error("账户验证失败");
        }

        return R.ok();
    }

    /**
     * 退出方法
     * @return
     */
    @GetMapping("/logout")
    public String logout(){
        ShiroUtils.logout();
        return "redirect:login.html";
    }

}

5. shiro缓存的配置 文件ehcache.xml

<ehcache>
    <diskStore path="java.io.tmpdir"/>
    <defaultCache

            maxEntriesLocalHeap="2000"
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true" />
</ehcache>

Ehcache配置文件的详细说明
[http://blog.csdn.net/mlitsn/article/details/1909192](http://blog.csdn.net/mlitsn/article/details/1909192)

 缓存存活时间和失效时间:
[http://www.cnblogs.com/sprinkle/p/6539086.html](http://www.cnblogs.com/sprinkle/p/6539086.html) 

设置缓存的大小
[http://elim.iteye.com/blog/2116749](http://elim.iteye.com/blog/2116749)

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

推荐阅读更多精彩内容

  • 一、什么是shiro Apache Shiro是一个全面的、蕴含丰富功能的安全框架。下图为描述Shiro功能的框架...
    夏日橘子冰阅读 415评论 0 1
  • 一、前言 我们在学习springboot的时候说过,要完成某项功能,引入一些依赖,我们第一反应就是这个功能看看有没...
    蓝雄威阅读 1,779评论 0 11
  • 强烈推荐老炮儿,不会失望的,看得很痛快。 最喜欢的一场戏是最后六爷骑自行车去约架的路上,遇到那只从笼子里逃出来的鸵...
    筑牙阅读 505评论 2 5
  • 午夜, 带着孤独者的失忆, 像疯了的蔓藤, 自由伸展,无法入睡, 失眠……
    墨凡M阅读 104评论 0 1
  • 1.我想拥有的:妈妈不再插手我的生活。 2.行为:如果妈妈不在插手我的生活,我就不会那么焦虑,可以心平气和地和妈妈...
    疏桐_sw阅读 303评论 0 3