Shiro框架学习笔记

doGetAuthorizationInfo和doGetAuthenticationInfo

    这两个方法虽然名字很像,但是意义是不一样的,doGetAuthorizationInfo方法是进行权限验证,doGetAuthenticationInfo是进行身份验证的(登录验证)


Realm就是负责验证的东西,可以是HashMap,SimpleAccountRealm,ini文件,数据库等 最常用的还是数据库.

简单验证

1.构建SecurityManager环境

    DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();

2.为SecurityManager设置检测源

defaultSecurityManager.setRealm(simpleAccountRealm);

3.设置SecurityManager

SecurityUtils.setSecurityManager(defaultSecurityManager);

4.获得用户主体

Subject subject=SecurityUtils.getSubject();

5.根据用户密码获得token

UsernamePasswordToken token=new UsernamePasswordToken("Kenny","123456");

6.根据登陆

subject.login(token);

7.检测是否登陆成功和权限问题

subject.isAuthenticated()    return boolean

subject.checkRole("admin");检测角色

subject.checkPermission("user:update");检测权限

通过INI文件验证

IniRealm iniRealm=new IniRealm("classpath:user.ini");

ini文件内容如下:

[users]

Kenny=111,admin

[roles]

admin=user:delete,user:update

剩下的和之前一样

通过数据库验证

1.初始化JdbcRealm

JdbcRealm jdbcRealm=new JdbcRealm();

2.为JdbcRealm设置数据源

jdbcRealm.setDataSource(druidDataSource);

3.设置自定义sql语句查询是否有该用户

String sql="select password from test_user where username=?";

jdbcRealm.setAuthenticationQuery(sql);

4.设置自定义sql语句查询是否有该角色

String sql="select password from test_user where username=?";

String roleSql="select role_name from test_user_role where user_name=?";

jdbcRealm.setUserRolesQuery(roleSql);

后面都一致


Shiro框架结合Springboot2.X

1.在pom.xml文件中导入必要的依赖

spring-boot-starter-web

spring-boot-starter-test

spring-boot-starter-thymeleaf

shiro-core

shiro-spring

mysql-connector-java

druid(阿里巴巴的)

下面两个是打开aop,实现注解管理权限功能

spring-boot-starter-aop

aspectjweaver

redis所需要的依赖(在日常开发问题文章中有写)

fastjson


2.设置自己的Realm

package com.kenny.shiroweb.realm;

import com.kenny.shiroweb.dao.UserDao;

import com.kenny.shiroweb.dao.UserRoleDao;

import com.kenny.shiroweb.vo.User;

import com.kenny.shiroweb.vo.UserRole;

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.authz.AuthorizationInfo;

import org.apache.shiro.authz.SimpleAuthorizationInfo;

import org.apache.shiro.crypto.hash.Md5Hash;

import org.apache.shiro.realm.AuthorizingRealm;

import org.apache.shiro.subject.PrincipalCollection;

import org.apache.shiro.util.ByteSource;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import java.util.*;

@Component

public class CustomRealmextends AuthorizingRealm {

MapuserMap=new HashMap(16);

    {

userMap.put("Kenny","672332263137c302ab54e66efd791c1b");

        super.setName("customRealm");

    }

@Autowired

    private UserDaouserDao;

    @Autowired

    private UserRoleDaoroleDao;

    protected AuthorizationInfodoGetAuthorizationInfo(PrincipalCollection principalCollection) {

String userName= (String) principalCollection.getPrimaryPrincipal();

        //从数据库或者缓存中获取角色数据

        Set roles=getRolesByUserName(userName);

        Set permissions=getPermissionsByUserName(userName);

        SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();

        simpleAuthorizationInfo.setRoles(roles);

        simpleAuthorizationInfo.setStringPermissions(permissions);

        return simpleAuthorizationInfo;

    }

protected AuthenticationInfodoGetAuthenticationInfo(AuthenticationToken authenticationToken)throws AuthenticationException {

//1.从主体传来得认证信息中,获得用户名

        String userName=(String)authenticationToken.getPrincipal();

        //2.通过用户名到数据库中获取凭证

        String password=getPassworkByUserName(userName);

        if(password==null){

return null;

        }

SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(userName,password,"customRealm");

        authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(userName));

        return authenticationInfo;

    }

private SetgetPermissionsByUserName(String userName) {

Set sets=new HashSet();

        sets.add("user:delete");

        sets.add("user:add");

        return sets;

    }

private SetgetRolesByUserName(String userName) {

System.out.println("从数据库中获取授权数据");

        List list=roleDao.findByUsername(userName);

        List role=new ArrayList<>();

        for(UserRole userRole:list){

role.add(userRole.getRolename());

        }

Set sets=new HashSet<>(role);

        return sets;

    }

private StringgetPassworkByUserName(String userName) {

List user=userDao.findByUsername(userName);

        if(user!=null){

return user.get(0).getPassword();

        }

return null;

    }

public static void main(String[] args){

Md5Hash md5Hash=new Md5Hash("123","viewer");

        System.out.println(md5Hash.toString());

    }

}

密码加密方式是通过md5加盐(用户名)的形式进行加密


2.进行shiro配置


package com.kenny.shiroweb.config;

import com.kenny.shiroweb.cache.RedisCacheManager;

import com.kenny.shiroweb.filter.RolesOrFilter;

import com.kenny.shiroweb.realm.CustomRealm;

import com.kenny.shiroweb.session.CustomSessionManger;

import com.kenny.shiroweb.session.RedisSessionDao;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;

import org.apache.shiro.codec.Base64;

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.CookieRememberMeManager;

import org.apache.shiro.web.mgt.DefaultWebSecurityManager;

import org.apache.shiro.web.servlet.SimpleCookie;

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 org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;

import java.util.LinkedHashMap;

import java.util.Map;

@Configuration

public class ShiroConfig {

@Bean

    public ShiroFilterFactoryBeanshirFilter(SecurityManager securityManager) {

System.out.println("ShiroConfiguration.shirFilter()");

        ShiroFilterFactoryBean shiroFilterFactoryBean =new ShiroFilterFactoryBean();

        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //自定义拦截器

        Map filtersMap =new LinkedHashMap();

        filtersMap.put("rolesOrFilter", new RolesOrFilter());

        shiroFilterFactoryBean.setFilters(filtersMap);

        //拦截器.

        Map filterChainDefinitionMap =new LinkedHashMap();

        // 配置不会被拦截的链接 顺序判断

        filterChainDefinitionMap.put("/static/**", "anon");

        filterChainDefinitionMap.put("/subLogin", "anon");

        filterChainDefinitionMap.put("/isLogin", "anon");

        //测试权限访问

        filterChainDefinitionMap.put("/testConfigRole", "rolesOrFilter[user,admin]");

        //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了

        filterChainDefinitionMap.put("/logout.html", "logout");

        //:这是一个坑呢,一不小心代码就不好使了;

//

        filterChainDefinitionMap.put("/**", "authc");

        // 如果不设置默认会自动寻找Web工程根目录下的"/login.html"页面

        shiroFilterFactoryBean.setLoginUrl("/login.html");

        // 登录成功后要跳转的链接

        shiroFilterFactoryBean.setSuccessUrl("/index.html");

        //未授权界面;

        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;

    }

//选择加密方式

    @Bean

    public HashedCredentialsMatcherhashedCredentialsMatcher(){

HashedCredentialsMatcher hashedCredentialsMatcher =new HashedCredentialsMatcher();

        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;

        hashedCredentialsMatcher.setHashIterations(1);//散列的次数,比如散列两次,相当于 md5(md5(""));

        return hashedCredentialsMatcher;

    }

//将自定义的reamlm注册为bean,并通过hashedCredentialsMatcher进行加密

    @Bean

    public CustomRealmcustomRealm(){

CustomRealm customRealmRealm =new CustomRealm();

        customRealmRealm.setCredentialsMatcher(hashedCredentialsMatcher());

        return customRealmRealm;

    }

/**

* 项目自定义的DAO

*/

    @Bean

    public RedisSessionDaoredisSessionDao() {

return new RedisSessionDao();

    }

/**

* 自定义的CacheManager

    * @return

    */

    @Bean

    public RedisCacheManagerredisCacheManager() {

return new RedisCacheManager();

    }

//自定义sessionmanager

    @Bean("sessionManager")

public CustomSessionMangersessionManager() {

CustomSessionManger sessionManager =new CustomSessionManger();

        sessionManager.setSessionDAO(redisSessionDao());

        return sessionManager;

    }

/**

* 设置记住我的cookie

    * @return

    */

    @Bean

    public SimpleCookierememberMeCookie(){

//System.out.println("ShiroConfiguration.rememberMeCookie()");

//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe

        SimpleCookie simpleCookie =new SimpleCookie("rememberMe");

        //

        simpleCookie.setMaxAge(259200);

        return simpleCookie;

    }

/**

* cookie管理对象;

* rememberMeManager()方法是生成rememberMe管理器,而且要将这个rememberMe管理器设置到securityManager中

    * @return

    */

    @Bean

    public CookieRememberMeManagerrememberMeManager(){

//System.out.println("ShiroConfiguration.rememberMeManager()");

        CookieRememberMeManager cookieRememberMeManager =new CookieRememberMeManager();

        cookieRememberMeManager.setCookie(rememberMeCookie());

        //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)

        cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));

        return cookieRememberMeManager;

    }

@Bean

    public DefaultWebSecurityManagersecurityManager(){

DefaultWebSecurityManager securityManager =new DefaultWebSecurityManager();

        securityManager.setRealm(customRealm());

        securityManager.setSessionManager(sessionManager());

        securityManager.setCacheManager(redisCacheManager());

        securityManager.setRememberMeManager(rememberMeManager());

        return securityManager;

    }

/**

* Shiro生命周期处理器

    * @return

    */

    @Bean

    public LifecycleBeanPostProcessorlifecycleBeanPostProcessor(){

return new LifecycleBeanPostProcessor();

    }

/**

* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证

* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能

    * @return

    */

    @Bean

    @DependsOn({"lifecycleBeanPostProcessor"})

public DefaultAdvisorAutoProxyCreatoradvisorAutoProxyCreator(){

DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator =new DefaultAdvisorAutoProxyCreator();

        advisorAutoProxyCreator.setProxyTargetClass(true);

        return advisorAutoProxyCreator;

    }

@Bean

    public AuthorizationAttributeSourceAdvisorauthorizationAttributeSourceAdvisor() {

AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =new AuthorizationAttributeSourceAdvisor();

        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());

        return authorizationAttributeSourceAdvisor;

    }

}

还有redis的配置文件不在这写了


通过注解在控制层进行拦截

//必须要admin角色才能访问

@RequiresRoles("admin") 

//要求subject中必须同时含有file:read和write:aFile.txt的权限才能执行方法

@RequiresPermissions({"user:delete", "user:add"} )


自定义拦截器

创建一个类继承AuthorizationFilter并重新他的isAccessAllowed方法,然后在ShiroConfig文件中配置

//自定义拦截器

Map filtersMap =new LinkedHashMap();

filtersMap.put("rolesOrFilter", new RolesOrFilter());

shiroFilterFactoryBean.setFilters(filtersMap);


自定义拦截器代码

package com.kenny.shiroweb.filter;

import org.apache.shiro.subject.Subject;

import org.apache.shiro.web.filter.authz.AuthorizationFilter;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class RolesOrFilterextends AuthorizationFilter {

@Override

    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o)throws Exception {

Subject subject=getSubject(servletRequest,servletResponse);

        String[] roles=(String[])o;

        if(roles==null||roles.length==0){

return true;

        }

for (String role : roles){

if(subject.hasRole(role)){

return true;

            }

}

return false;

    }

}

在ShiroConfig中使用:

filterChainDefinitionMap.put("/testConfigRole", "rolesOrFilter[user,admin]");


自定义SessionManger

在ShiroConfig中注册为bean

package com.kenny.shiroweb.session;

import org.apache.shiro.session.Session;

import org.apache.shiro.session.UnknownSessionException;

import org.apache.shiro.session.mgt.SessionKey;

import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;

import org.apache.shiro.web.session.mgt.WebSessionKey;

import javax.servlet.ServletRequest;

import java.io.Serializable;

public class CustomSessionMangerextends DefaultWebSessionManager {

@Override

    protected SessionretrieveSession(SessionKey sessionKey)throws UnknownSessionException {

Serializable sessionId=getSessionId(sessionKey);

        ServletRequest request=null;

        if(sessionKeyinstanceof WebSessionKey){

request=((WebSessionKey)sessionKey).getServletRequest();

        }

if(request!=null&&sessionId!=null){

Session session=(Session)request.getAttribute(sessionId.toString());

            if(session!=null){

return session;

            }

}

Session session=super.retrieveSession(sessionKey);

        if(request!=null&&sessionId!=null){

request.setAttribute(sessionId.toString(),session);

        }

return session;

    }


自定义SessionDao

新建一个类继承AbstractSessionDAO并重写他的方法

主要还是增删改查


key以kenny-session:为前缀加上sessionid

value:为session的序列化

因为redis中不能直接存储对象,需要将他序列化SerializationUtils.serialize(session);

当需要从redis中取出来的时候再反序列化强转为Session对象即可.

然后将自定义的SessionDao注册为Bean,

在刚自定义的CustomSessionManger添加自定义的DAO

sessionManager.setSessionDAO(redisSessionDao());


自定义CacheManager

新建一个类并实现CacheManager接口

还是通过redis进行缓存,与SessionManager类似,只是存储的对象不一样

将刚自定义的redisCacheManager在ShiroConfig中注册为bean


记住我功能

在ShrioConfig文件中添加两个Bean

    /**

* 设置记住我的cookie

* @return

*/

@Bean

public SimpleCookierememberMeCookie(){

//System.out.println("ShiroConfiguration.rememberMeCookie()");

//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe

    SimpleCookie simpleCookie =new SimpleCookie("rememberMe");

    //

    simpleCookie.setMaxAge(259200);

    return simpleCookie;

}

/**

* cookie管理对象;

* rememberMeManager()方法是生成rememberMe管理器,而且要将这个rememberMe管理器设置到securityManager中

* @return

*/

@Bean

public CookieRememberMeManagerrememberMeManager(){

//System.out.println("ShiroConfiguration.rememberMeManager()");

    CookieRememberMeManager cookieRememberMeManager =new CookieRememberMeManager();

    cookieRememberMeManager.setCookie(rememberMeCookie());

    //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)

    cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));

    return cookieRememberMeManager;

}

在securityManager中设置这个管理器

securityManager.setRememberMeManager(rememberMeManager());

在控制层上通过

token.setRememberMe(user.isRememberMe());来决定是否开启这个功能

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

推荐阅读更多精彩内容