Shiro的常见用法


 

在Shiro中使用Filter

shiro内置了很多Filter,常见的如下

  • authc:FormAuthenticationFilter,表单认证过滤器,需要从表单登录认证后才能访问指定的url
  • user:UserFilter,用户过滤器,要是特定用户才能访问指定的url
  • anon:AnonymousFilter,匿名过滤器,不需要登录即可访问指定页面,常用于游客可以访问的页面
  • roles:RolesAuthorizationFilter,角色过滤器,具有指定角色的用户才能访问指定的url。如果设置多个角色, eg. roles[“admin,user”],用户必须同时具有这些角色才能通过
  • perms:PermissionsAuthorizationFilter,权限过滤器,具有指定权限的用户才能访问指定的url。如果设置多个权限, eg. perms[“watch, download”],用户必须同时具有这些权限才能通过
  • logout:LogoutFilter,退出过滤器,会直接跳转到 shiroFilterFactoryBean.setLoginUrl() 设置的页面
  • ssl:SslFilter,ssl拦截器,请求协议要是https才能通过

对应关系可在DefaultFilter类中查看。也可以使用自定义的Filter

 

Filter指定要拦截的url时可以使用通配符

  • ?   匹配除/之外的任意一个字符
  • *   匹配零个或多个字符,但这些字符中不能包含/
  • **   和*差不多,但可以包含/

 

Shiro的加密、解密

数据库一般加密存储用户密码,Shiro身份认证时传入密码,需要将传入的密码(明文)加密后与数据库中存储的密码比较。

当然,也可以不使用shiro的加密,直接传入密文。
 

Shiro常用的加密

String pwd = "abcd1234";

//sha256加密,64位
String sha256Pwd = new Sha256Hash(pwd).toHex();
// String sha256Pwd = new Sha256Hash(pwd).toString();

//md5加密,32位
String md5Pwd = new Md5Hash(pwd).toHex();
// String md5Pwd = new Md5Hash(pwd).toString();

toString()、toHex()是一样的,因为toString()就是调用toHex()。

 

Shiro的Session模块

SessionManager:会话管理器,可以管理session中的用户认证、授权信息
 

RememberMe

  • 在调用subject.login()之前,设置 token.setRememberMe(true)
  • 实质是通过cookie在浏览器本地保存用户信息
     

常见的判断方法

  • subject.isAuthenticated()  用户是否是通过身份验证登录的,即通过Subject.login() 登录的
  • subject.isRemembered()  用户是否是通过RememberMe登录的

这2个是互斥的,要么是通过login()登录的,要么是通过rememberMe登录的

rememberMe方式会在浏览器本地保存用户信息,不安全,对安全性要求高的项目,比如银行的项目,一律使用login()。

 

Shiro的缓存模块

使用缓存模块的原因

  • tomcat集群之后,要使用分布式session,一般引入redis做分布式session,session中的认证信息也会被缓存
  • 提高性能:从缓存中获取用户授权信息,不必每次都从数据库查询
  • 持久化:web应用停止后,session被销毁,tomcat中的用户认证信息丢失;使用redis后,重启应用,用户认证信息仍在

可以借助spring session等手段将授权信息放到redis上,但需要自己写代码实现认证信息的序列化、反序列化,很麻烦。

shiro的缓存模块提供了 RedisSessionDAO ,可以自动完成web服务器、redis之间用户认证信息的序列化、反序列化。

认证信息的缓存默认关闭,授权信息的缓存默认开启,因为用户登录次数不多,而访问页面时往往需要校验用户授权。
 

注意事项

  • User、Role、Permission这3个数据表对应的实体类都要实现Serializable接口,成为可序列化的
  • 登出时,在调用subject.logout()后,需删除redis中对应的key,即删除对应的token

 

Shiro的常见用法

依赖
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.5.3</version>
</dependency>

<dependency>
    <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis</artifactId>
    <version>3.2.3</version>
</dependency>

 
新建包shiro。不知道导入哪个类时,导入shiro中的类。
 

自定义的Realm

新建类CustomRealm,继承AuthorizingRealm

/**
 * 自定义的Realm
 */
 @Component
public class CustomRealm extends AuthorizingRealm {

	@Resource
	private UserDao userDao;
	
    /**
     * 授权方法,权限校验时自动调用
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取主体标识
        String username = (String) principalCollection.getPrimaryPrincipal();

        //使用dao层,根据主体标识查询用户对应的角色、权限,此处略过
        Set<String> permissions = null;
        Set<String> roles = null;

        //设置并返回主体的授权信息
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setRoles(roles);
        simpleAuthorizationInfo.setStringPermissions(permissions);

        return simpleAuthorizationInfo;
    }


    /**
     * 认证方法,登录时自动调用
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取主体标识
        String username = (String) token.getPrincipal();

        //校验账号、密码是否匹配,不匹配直接返回null
        UserPo userPo = userDao.getByUsername(username);
        if(userPo == null) {
        	return null;
        }
        String password = userPo.getPassword();
        
        //设置并返回主体的认证信息,主体标识、凭证、realm的名称
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, this.getName());

        return simpleAuthenticationInfo;
    }

}

 

自定义的Filter

新建类CustomRolesOrAuthorizationFilter ,继承AuthorizationFilter

/**
 * 自定义filter
 */
public class CustomRolesOrAuthorizationFilter extends AuthorizationFilter {

    /**
     * 权限校验
     * @param request
     * @param response
     * @param mappedValue Object类型,所需的角色/权限
     * @return boolean 是否通过验证
     * @throws IOException
     */
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {

        Subject subject = getSubject(request, response);

        //获取当前访问路径所需要的角色集合
        String[] rolesArray = (String[]) mappedValue;

        //没有角色限制,可以直接访问
        if (rolesArray == null || rolesArray.length == 0) {
            return true;
        }

        Set<String> roles = CollectionUtils.asSet(rolesArray);

        //具有roles中的任意一个角色,则有权限访问
        for(String role : roles){
            if(subject.hasRole(role)){
                return true;
            }
        }

        return false;
    }

}

 

自定义的SessionManager

新建类CustomSessionManager,继承DefaultWebSessionManager

public class CustomSessionManager extends DefaultWebSessionManager {

    private static final String AUTHORIZATION = "token";

    public CustomSessionManager(){
        super();
    }

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        if(sessionId != null){
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
                    ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return sessionId;
        }else {
            return super.getSessionId(request,response);
        }
    }
    
}

 

Shiro配置类

新建类ShiroConfig

@Configuration
public class ShiroConfig {

    /** 配置ShiroFilterFactoryBean,主要是设置Filter的过滤规则
     * @param securityManager
     * @return ShiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        //设置securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //某些页面需要登录后才能访问,如果未登录,会交给指定的url处理
        //如果是前后端分离,设置为controller映射的url;如果前后端未分离,设置为视图页面的url
        shiroFilterFactoryBean.setLoginUrl("/pub/need_login");

        //登录成功后跳转到的url
        //如果前后端分离,则不用设置此项
        //shiroFilterFactoryBean.setSuccessUrl("/");

        //没有权限、未授权会交给指定的url处理,处理方式一般是:先验证登录,再验证是否有权限
        //如果前后端没有分离,直接设置为视图页面的url
        shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit");


        //设置自定义的Filter
        Map<String,Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
        shiroFilterFactoryBean.setFilters(filterMap);


        //Filter的执行有一定顺序,应该使用LinkedHashMap,使用HashMap会出问题
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        //退出过滤器
        filterChainDefinitionMap.put("/logout", "logout");

        //不需要登录(校验)就可以访问
        filterChainDefinitionMap.put("/pub/**", "anon");

        //用户登录后才可以访问
        filterChainDefinitionMap.put("/authc/**", "authc");

        //角色是管理员的用户才可以访问
        filterChainDefinitionMap.put("/admin/**", "roleOrFilter[admin]");

        //具有video_update权限的用户才可以访问
        filterChainDefinitionMap.put("/video/dwonload", "perms[video_download]");

        //[]表示参数是数组,有多个元素时逗号隔开,eg. [video_watch,video_download],必须同时满足[]中的要求才会通过

        //LinkedHashMap,Filter的执行顺序和添加顺序一致,一般把 /** 放到最后

        //前面的url未匹配的请求路径,就使用/**来过滤
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }


    /**
     * 获取redisManager
     * @return RedisManager
     */
    public RedisManager redisManager(){
        RedisManager redisManager = new RedisManager();
        redisManager.setHost("127.0.0.1:6379");  //设置redis服务器的地址,默认就是"127.0.0.1:6379"
        return redisManager;
    }


    /**
     * 获取RedisSessionDAO
     * @return RedisSessionDAO
     */
    public RedisSessionDAO redisSessionDAO(){
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }


    /**
     * 获取自定义的sessionManager
     * @return SessionManager
     */
    @Bean
    public SessionManager sessionManager(){

        CustomSessionManager customSessionManager = new CustomSessionManager();

        //设置session超时时间,单位ms。默认30min
        //customSessionManager.setGlobalSessionTimeout(20000);

        //配置session持久化
        customSessionManager.setSessionDAO(redisSessionDAO());

        return customSessionManager;
    }


    /**
     * 获取RedisCacheManager
     * @return RedisCacheManager
     */
    public RedisCacheManager cacheManager(){
        RedisCacheManager redisCacheManager = new RedisCacheManager();

        redisCacheManager.setRedisManager(redisManager());

        //设置授权信息对应的key的过期时间,单位s。用户权限有变化时,除了要更新数据库,还需要更新redis中对应的key
        redisCacheManager.setExpire(24*60*60*7);

        return redisCacheManager;
    }


    /**
     * 设置加密、解密规则
     * @return HashedCredentialsMatcher
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();

        //设置使用的散列算法是md5
        credentialsMatcher.setHashAlgorithmName("md5");

        //设置散列次数,即多重加密次数
        credentialsMatcher.setHashIterations(2);

        return credentialsMatcher;
    }


    /**
     * 获取自定义的Realm
     * @return CustomRealm
     */
    @Bean
    public CustomRealm customRealm(){
        CustomRealm customRealm = new CustomRealm();
        customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return customRealm;
    }


    /**
     * 配置SecurityManager
     * @return SecurityManager
     */
    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        //如果前后端没有分离,则不必使用sessionManager,不必设置SessionManager相关配置
        securityManager.setSessionManager(sessionManager());

        //使用自定义的CacheManager
        securityManager.setCacheManager(cacheManager());

        //设置Realm。建议放到最后,否则可能会出问题
        securityManager.setRealm(customRealm());

        return securityManager;
    }

}

 

Shiro的权限校验注解

在controller或controller的方法上加注解,请求对应接口时会自动校验权限,此种方式权限控制散落在controller中,与业务代码耦合在一起,不好维护,一般不用,了解即可

//校验角色|权限,要有指定的角色|权限才能访问,value中配置多个角色|权限时,可用logical指定逻辑运算符
@RequiresRoles(value={"admin", "editor"}, logical= Logical.AND)
@RequiresPermissions (value={"user:add", "user:del"}, logical= Logical.OR)

//要是已授权的用户才能访问(即调用Subject.isAuthenticated()返回true)
@RequiresAuthentication

//要是已登录的用户才能访问(已通过login()或rememberMe登录)
@RequiresUser

 

说明

常用的鉴权方式

  • 分布式session
  • JWT
  • Oauth2.0
     

复杂的权限校验会拉低性能,不要使用过于复杂的权限校验,尤其是对性能要求较高的项目,eg. 高并发的秒杀系统。
 

没有绝对可靠的校验方式,但可以用以下方式增加安全性

  • 限制一定时间内同一ip登录错误的次数
  • 增加图形验证码,图形验证码不能过于简单,过于简单容易被脚本识别
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值