资讯专栏INFORMATION COLUMN

Spring Boot 整合 Shiro

newsning / 2676人阅读

摘要:虽然,直接用和进行全家桶式的合作是最好不过的,但现实总是欺负我们这些没办法决定架构类型的娃子。并非按输入顺序。遍历时只能全部输出,而没有顺序。设想以下,若全局劫持在最前面,那么只要在裆下的,都早早被劫持了。底层是数组加单项链表加双向链表。

虽然,直接用Spring Security和SpringBoot 进行“全家桶式”的合作是最好不过的,但现实总是欺负我们这些没办法决定架构类型的娃子。

Apache Shiro 也有其特殊之处滴。若需了解,可以转战到[Apache Shiro 简介]

1. 添加Shiro依赖

shiro的版本,看个人喜好哈,本文的版本为:

<shiro.version>1.3.2shiro.version>
<dependency>
    <groupId>org.apache.shirogroupId>
    <artifactId>shiro-coreartifactId>
    <version>${shiro.version}version>
dependency>
<dependency>
    <groupId>org.apache.shirogroupId>
    <artifactId>shiro-webartifactId>
    <version>${shiro.version}version>
dependency>
<dependency>
    <groupId>org.apache.shirogroupId>
    <artifactId>shiro-aspectjartifactId>
    <version>${shiro.version}version>
dependency>
<dependency>
    <groupId>org.apache.shirogroupId>
    <artifactId>shiro-ehcacheartifactId>
    <version>${shiro.version}version>
dependency>
<dependency>
    <groupId>org.apache.shirogroupId>
    <artifactId>shiro-quartzartifactId>
    <version>${shiro.version}version>
dependency>
<dependency>
    <groupId>org.apache.shirogroupId>
    <artifactId>shiro-springartifactId>
    <version>${shiro.version}version>
dependency>
2. shiroRealm

授权认证具体实现之地。通过继承 AuthorizingRealm 进而实现,对登录时的账号密码校验功能

@Slf4j
public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    private ShiroPermissionRepository shiroPermissionRepository;

    /**
     * 授权
     *
     * @param principalCollection 主要信息
     * @return 授权信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        if (log.isInfoEnabled()){
            log.info("Authorization begin");
        }
        String name= (String) principalCollection.getPrimaryPrincipal();
        List role = shiroPermissionRepository.queryRoleByName(name);
        if (role.isEmpty()){
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            simpleAuthorizationInfo.addRoles(role);
            return simpleAuthorizationInfo;
        }
        return null;
    }

    /**
     * 认证
     *
     * @param authenticationToken 认证token
     * @return 认证结果
     * @throws AuthenticationException 认证异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        if (log.isInfoEnabled()){
            log.info("Authentication begin");
        }

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        Object principal =token.getPrincipal();
        Object credentials = token.getCredentials();

        //校验用户名
        checkBlank(principal,"用户名不能为空");
        //校验密码
        checkBlank(credentials,"密码不能为空");

        //校验姓名
        String username = (String) principal;
        UserPO userPO = shiroPermissionRepository.findAllByName(username);
        if (userPO == null){
            throw new AccountException("用户名错误");
        }

        //校验密码
        String password = (String) credentials;
        if (!StringUtils.equals(password,userPO.getPassword())){
            throw new AccountException("密码错误");
        }

        return new SimpleAuthenticationInfo(principal, password, getName());
    }

    private void checkBlank(Object obj,String message){
        if (obj instanceof String){
            if (StringUtils.isBlank((String) obj)){
                throw new AccountException(message);
            }
        }else if (obj == null){
            throw new AccountException(message);
        }
    }
}
3. 配置ShiroConfig

将ShiroConfig、SecurityManager、ShiroFilterFactoryBean交给Spring管理.

ShiroRealm: 则上述所描述的ShiroRealm

SecurityManager: 管理 所有用户 的安全操作

ShiroFilterFactoryBean: 配置Shiro的过滤器

@Configuration
public class ShiroConfig {

    private final static String AUTHC_STR = "authc";
    private final static String ANON_STR = "anon";

    /**
     * 验证授权、认证
     *
     * @return shiroRealm 授权认证
     */
    @Bean
    public ShiroRealm shiroRealm(){
        return new ShiroRealm();
    }

    /**
     * session manager
     *
     * @param shiroRealm  授权认证
     * @return  安全管理
     */
    @Bean
    @ConditionalOnClass(ShiroRealm.class)
    public SecurityManager securityManager(ShiroRealm shiroRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroRealm);
        return securityManager;
    }

    /**
     * Filter工厂,设置对应的过滤条件和跳转条件
     *
     * @param securityManager session 管理
     * @return shiro 过滤工厂
     */
    @Bean
    @ConditionalOnClass(value = {SecurityManager.class})
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        shiroFilterFactoryBean.setFilters(filterMap);

        //URI过滤
        Map map = Maps.newLinkedHashMap();

        //可过滤的接口路径
        

        //所有API路径进行校验
        map.put("/api/**",AUTHC_STR);

        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        return shiroFilterFactoryBean;
    }
}
3.1 Shiro 过滤器小插曲

shiro和security也有相似之处,都有自己的 filter chain。翻一番Shiro的源码,追溯一下。发下以下:

3.1.1 ShiroFilterFactoryBean——createFilterChainManager

  protected FilterChainManager createFilterChainManager() {

        DefaultFilterChainManager manager = new DefaultFilterChainManager();
        Map defaultFilters = manager.getFilters();
        //apply global settings if necessary:
        for (Filter filter : defaultFilters.values()) {
            applyGlobalPropertiesIfNecessary(filter);
        }

        //Apply the acquired and/or configured filters:
        Map filters = getFilters();
        if (!CollectionUtils.isEmpty(filters)) {
            for (Map.Entry entry : filters.entrySet()) {
                String name = entry.getKey();
                Filter filter = entry.getValue();
                applyGlobalPropertiesIfNecessary(filter);
                if (filter instanceof Nameable) {
                    ((Nameable) filter).setName(name);
                }
                //"init" argument is false, since Spring-configured filters should be initialized
                //in Spring (i.e. "init-method=blah") or implement InitializingBean:
                manager.addFilter(name, filter, false);
            }
        }

        //build up the chains:
        Map chains = getFilterChainDefinitionMap();
        if (!CollectionUtils.isEmpty(chains)) {
            for (Map.Entry entry : chains.entrySet()) {
                String url = entry.getKey();
                String chainDefinition = entry.getValue();
                manager.createChain(url, chainDefinition);
            }
        }

        return manager;
    }

从源码可以发现,shiro的过滤器链,添加顺序是:

    defaultFilters: shiro默认的过滤器链

    filters: 咱们自定义的过滤器链

    chains:明确指定要过滤的

3.1.2 DefaultFilterChainManager —— addDefaultFilters

这里咱看看DefaultFilterChainManager 到底添加了那些默认过滤器链,可以看到主要的是:DefaultFilter

protected void addDefaultFilters(boolean init) {
    for (DefaultFilter defaultFilter : DefaultFilter.values()) {
        addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
    }
}

3.1.3 DefaultFilter

anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class);
4. 测它

由于设置对全局接口进行校验,因此,预期结果就是不能够访问啦

map.put("/api/**",AUTHC_STR);
4.1 IDAL
@RestController
@RequestMapping( SYSTEM_API +"shiro")
public class ShiroIdal {

    @Resource
    private IShiroService iShiroService;


    @GetMapping
    public HttpEntity obtain(@RequestParam String name){
        return iShiroService.obtainUserByName(name);
    }
}
4.2 service
@Slf4j
@Service
public class ShiroServiceImpl implements IShiroService {

    @Resource
    private ShiroPermissionRepository shiroPermissionRepository;
	
    public HttpEntity obtainUserByName(String name) {
        UserPO userPO = shiroPermissionRepository.findAllByName(name);
        return HttpResponseSupport.success(userPO);
    }
}
4.3 被劫持的情况

若没 login.jsp,则会直接报错,个人觉得太不和谐了,毕竟现在都是前后端分离的。

4.4 设置允许访问

在URI过滤Map加入以下:

map.put("/api/shiro",ANON_STR);

注意: 要在“全局Api劫持”前添加。而且不要使用“HashMap”,为什么?

4.4.1 HashMap

在说为什么前,先了解HashMap这货是什么原理先。

for (Entry entry : hashMap.entrySet()) {
   MessageFormat.format("{0}={1}",entry.getKey(),entry.getValue());
}

HashMap散列图是按“有利于随机查找的散列(hash)的顺序”。并非按输入顺序。遍历时只能全部输出,而没有顺序。甚至可以rehash()重新散列,来获得更利于随机存取的内部顺序。

这会影响shiro哪里呢?

Map chains = getFilterChainDefinitionMap();
if (!CollectionUtils.isEmpty(chains)) {
    for (Map.Entry entry : chains.entrySet()) {
        String url = entry.getKey();
        String chainDefinition = entry.getValue();
        manager.createChain(url, chainDefinition);
    }
}

ShiroFilterFactoryBean 中,在构建shiro的filter chain时,会对我们配置的FilterChainDefinitionMap 进行一次遍历,并且将其添加到DefaultFilterChainManager中。

设想以下,若“全局API劫持”在最前面,那么只要在/api/*裆下的,都早早被劫持了。轮得到配置的 anon 么?若由于HashMap的散列排序导致“全局API劫持”在最前面,emmmm,那玩锤子。

4.4.2 LinkedHashMap

因此,建议使用LinkedHashMap,为啥子?撸源码

   static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry before, after;
        Entry(int hash, K key, V value, Node next) {
            super(hash, key, value, next);
        }
    }
    transient LinkedHashMap.Entry head;
    transient LinkedHashMap.Entry tail;

内部类中多了两个Entry,一个记录前方entry,一个记录后方entry,这样的双向链表结构保证了插入顺序的有序。

LinkedHashMap底层是数组加单项链表加双向链表

数组加单向链表就是HashMap的结构,记录数据用,

双向链表,存储插入顺序用。

有点跑偏了,这些大伙肯定都知道滴了......

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/7193.html

相关文章

  • springboot整合shiro使用shiro-spring-boot-web-starter

    摘要:此文章仅仅说明在整合时的一些坑并不是教程增加依赖集成依赖配置三个必须的用于授权和登录创建自己的实例用于实现权限三种方式实现定义权限路径第一种使用角色名定义第二种使用权限定义第三种使用接口的自定义配置此处配置之后需要在对应的 此文章仅仅说明在springboot整合shiro时的一些坑,并不是教程 增加依赖 org.apache.shiro shiro-spring-...

    sevi_stuo 评论0 收藏0
  • 两年了,我写了这些干货!

    摘要:开公众号差不多两年了,有不少原创教程,当原创越来越多时,大家搜索起来就很不方便,因此做了一个索引帮助大家快速找到需要的文章系列处理登录请求前后端分离一使用完美处理权限问题前后端分离二使用完美处理权限问题前后端分离三中密码加盐与中异常统一处理 开公众号差不多两年了,有不少原创教程,当原创越来越多时,大家搜索起来就很不方便,因此做了一个索引帮助大家快速找到需要的文章! Spring Boo...

    huayeluoliuhen 评论0 收藏0
  • Spring Security

    摘要:框架具有轻便,开源的优点,所以本译见构建用户管理微服务五使用令牌和来实现身份验证往期译见系列文章在账号分享中持续连载,敬请查看在往期译见系列的文章中,我们已经建立了业务逻辑数据访问层和前端控制器但是忽略了对身份进行验证。 重拾后端之Spring Boot(四):使用JWT和Spring Security保护REST API 重拾后端之Spring Boot(一):REST API的搭建...

    keelii 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<