Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Shiro关闭session,无状态接入Springboot

Shiro关闭session,无状态接入Springboot

作者头像
taixingyiji
发布于 2022-07-25 08:46:47
发布于 2022-07-25 08:46:47
1.1K00
代码可运行
举报
运行总次数:0
代码可运行

# Shiro关闭session配置

# 前言

本文基于token进行身份认证,由于接入cas会和shiro的session管理冲突,所以关闭shiro的session,进行无状态管理。

特此记录一下shiro如何进行无状态管理。

# 一、引入依赖

此处引入的为 shiro-spring ,版本为 1.7.1

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.7.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.0</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

# 二、实现DefaultWebSubjectFactory

实现 DefaultWebSubjectFactory 关闭session

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.hcframe.base.module.shiro;

import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;

/**
 * @author lhc
 * @version 1.0
 * @className StatelessDefaultSubjectFactory
 * @date 2021年04月19日 1:54 下午
 * @description 描述
 */
public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {

    @Override
    public Subject createSubject(SubjectContext context) {
        //不创建session
        context.setSessionCreationEnabled(false);
        return super.createSubject(context);
    }
}

# 三、实现AuthenticationToken

此处是为了将用户信息改为token传递,通过token方式进行验证

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.hcframe.base.module.shiro;

import org.apache.shiro.authc.AuthenticationToken;

/**
 * @author lhc
 * @version 1.0
 * @className AuthToken
 * @date 2021年04月19日 2:56 下午
 * @description 实现shiro AuthenticationToken
 */
public class AuthToken implements AuthenticationToken {

    private String token;

    public AuthToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }
}

# 四、实现shiro的过滤器

此处为权限过滤器,具体内容参见注释

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.hcframe.base.module.shiro;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author lhc
 * @version 1.0
 * @className AuthFilter
 * @date 2021年04月19日 2:56 下午
 * @description 实现shiro 过滤器
 */
public class AuthFilter extends AuthenticatingFilter {

    /**
     * @author lhc
     * @description 创建token
     * @date 4:35 下午 2021/4/26
     * @params [request, response]
     * @return org.apache.shiro.authc.AuthenticationToken
     **/
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        //获取请求token
        String token = getRequestToken((HttpServletRequest) request);
        if (StringUtils.isBlank(token)) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            // 增加跨域支持
            String myOrigin = httpServletRequest.getHeader("origin");
            httpResponse.setContentType("application/json;charset=utf-8");
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with, X-Access-Token, datasource-Key");
            httpResponse.setHeader("Access-Control-Allow-Origin", myOrigin);
            httpResponse.setCharacterEncoding("UTF-8");
            // 返回错误状态信息
            Map<String, Object> result = new HashMap<>();
            result.put("code", 3);
            result.put("msg", "未登陆");
            String json = JSON.toJSONString(result);
            httpResponse.getWriter().print(json);
            return null;
        }
        return new AuthToken(token);
    }

    /**
     * @author lhc
     * @description 步骤1.所有请求全部拒绝访问
     * @date 4:37 下午 2021/4/26
     * @params [request, response, mappedValue]
     * @return boolean
     **/
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return ((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name());
    }

    /**
     * @author lhc
     * @description 步骤2,拒绝访问的请求,会调用onAccessDenied方法,onAccessDenied方法先获取 token,再调用executeLogin方法
     * @date 4:37 下午 2021/4/26
     * @params [request, response]
     * @return boolean
     **/
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //获取请求token,如果token不存在,直接返回
        String token = getRequestToken((HttpServletRequest) request);
        if (StringUtils.isBlank(token)) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            // 增加跨域支持
            String myOrigin = httpServletRequest.getHeader("origin");
            httpResponse.setContentType("application/json;charset=utf-8");
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with, X-Access-Token, datasource-Key");
            httpResponse.setHeader("Access-Control-Allow-Origin", myOrigin);
            httpResponse.setCharacterEncoding("UTF-8");
            // 返回错误状态信息
            Map<String, Object> result = new HashMap<>();
            result.put("code", 3);
            result.put("msg", "未登陆");
            String json = JSON.toJSONString(result);
            httpResponse.getWriter().print(json);
            return false;
        }
        return executeLogin(request, response);
    }

    /**
     * @author lhc
     * @description 登陆失败时候调用
     * @date 4:38 下午 2021/4/26
     * @params [token, e, request, response]
     * @return boolean
     **/
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        //处理登录失败的异常
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setContentType("application/json;charset=utf-8");
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String myOrigin = httpServletRequest.getHeader("origin");
        httpResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with, X-Access-Token, datasource-Key");
        httpResponse.setHeader("Access-Control-Allow-Origin", myOrigin);
        httpResponse.setCharacterEncoding("UTF-8");
        try {
            //处理登录失败的异常
            Throwable throwable = e.getCause() == null ? e : e.getCause();
            Map<String, Object> result = new HashMap<>();
            result.put("code", 3);
            result.put("msg", "未登陆");
            String json = JSON.toJSONString(result);
            httpResponse.getWriter().print(json);
        } catch (IOException e1) {
        }
        return false;
    }

    /**
     * @author lhc
     * @description 获取请求的token
     * @date 4:38 下午 2021/4/26
     * @params [httpRequest]
     * @return java.lang.String
     **/
    private String getRequestToken(HttpServletRequest httpRequest) {
        //从header中获取token
        String token = httpRequest.getHeader("X-Access-Token");
        //如果header中不存在token,则从参数中获取token
        if (StringUtils.isBlank(token)) {
            if (StringUtils.isBlank(token)) {
                token = httpRequest.getParameter("token");
            }
        }
        return token;
    }
}

# 五、编写自定义的Realm

编写自定义realm,此步骤是为了定义权限校验和用户信息验证。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.hcframe.base.module.shiro;


import com.hcframe.base.common.config.FrameConfig;
import com.hcframe.base.module.shiro.service.ShiroService;
import com.hcframe.base.module.shiro.service.SystemRealm;
import com.hcframe.redis.RedisUtil;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.AssertionImpl;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.Resource;
import java.util.Date;
import java.util.Map;


/**
 * @author lhc
 * @version 1.0
 * @className CustomRealm
 * @date 2021年04月19日 2:56 下午
 * @description 自定义Realm
 */
public class CustomRealm extends AuthorizingRealm {

    @Resource
    private RedisUtil redisUtil;

    @Autowired
    AuthService authService;

    @Resource
    private SystemRealm systemRealm;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        Object user = principalCollection.getPrimaryPrincipal();
        Map<String, Object> map = (Map<String, Object>) user;
        // 从数据库读取权限,注入到shiro中
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        Set<String> set = authService.getUserAuth(String.valueOf(map.get("ID")));
        for (String auth : set) {
            simpleAuthorizationInfo.addStringPermission(auth);
        }
        return simpleAuthorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
        String accessToken = (String) token.getPrincipal();
        //1. 根据accessToken,查询用户信息
        FtToken tokenEntity = shiroService.findByToken(accessToken);
        userId = tokenEntity.getUserId();
        //2. token失效
        if (tokenEntity.getExpireTime().getTime() < System.currentTimeMillis()) {
            throw new IncorrectCredentialsException("token失效,请重新登录");
        }
        
        //3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
        Object user = shiroService.findByUserId(userId);
        //4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
        if (user == null) {
            throw new UnknownAccountException("用户不存在!");
        }
        //5. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
        return new SimpleAuthenticationInfo(user, accessToken, this.getName());
    }

    @Override
    public boolean supports(AuthenticationToken authenticationToken) {
        return authenticationToken instanceof AuthToken;
    }
}

# 六、编写Shiro配置类

编写shiro配置类,将bean交给Spring管理

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.hcframe.base.module.shiro;

import com.hcframe.base.module.shiro.service.SystemRealm;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.DefaultSessionManager;
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.mgt.DefaultWebSubjectFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    /**
     * 不加这个注解不生效,具体不详
     */
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }

    /**
     * 将自己的验证方式加入容器
     */
    @Bean
    public CustomRealm myShiroRealm() {
        CustomRealm customRealm = new CustomRealm();
        customRealm.setCachingEnabled(false);
        return customRealm;
    }

    /**
     * @return org.apache.shiro.web.mgt.DefaultWebSubjectFactory
     * @author lhc
     * @description // 自定义subject工厂
     * @date 4:50 下午 2021/4/19
     * @params []
     **/
    @Bean
    public DefaultWebSubjectFactory subjectFactory() {
        return new StatelessDefaultSubjectFactory();
    }

    /**
     * @return org.apache.shiro.session.mgt.SessionManager
     * @author lhc
     * @description // 自定义session管理器
     * @date 5:50 下午 2021/4/19
     * @params []
     **/
    @Bean
    public SessionManager sessionManager() {
        DefaultSessionManager shiroSessionManager = new DefaultSessionManager();
        // 关闭session校验轮询
        shiroSessionManager.setSessionValidationSchedulerEnabled(false);
        return shiroSessionManager;
    }

    /**
     * 权限管理,配置主要是Realm的管理认证
     */
    @Bean("securityManager")
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 禁用session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        // 设置自定义subject工厂
        securityManager.setSubjectFactory(subjectFactory());
        // 设置自定义session管理器
        securityManager.setSessionManager(sessionManager());
        // 设置自定义realm
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }


    /**
     * Filter工厂,设置对应的过滤条件和跳转条件
     */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        Map<String, Filter> filters = new HashMap<>(1);
        // 设置自定义过滤器
        filters.put("auth", new AuthFilter());
        shiroFilterFactoryBean.setFilters(filters);
        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        // 用户登陆
        map.put("/ftUser/login", "anon");
        // Vue静态资源
        map.put("/img/**", "anon");
        map.put("/static/**", "anon");
        map.put("/tinymce/**", "anon");
        map.put("/favicon.ico", "anon");
        map.put("/manifest.json", "anon");
        map.put("/robots.txt", "anon");
        map.put("/precache*", "anon");
        map.put("/service-worker.js", "anon");
        // swagger UI 静态资源
        map.put("/swagger-ui.html","anon");
        map.put("/doc.html","anon");
        map.put("/swagger-resources/**","anon");
        map.put("/webjars/**","anon");
        map.put("/v2/api-docs","anon");
        map.put("/v2/api-docs-ext","anon");
        map.put("/swagger/**","anon");
        // druid 资源路径
        map.put("/druid/**","anon");
        // cas 接口
        map.put("/cas/valid","anon");
        map.put("/cas/logout","anon");
        // 其余路径均拦截
        map.put("/**", "auth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    /**
     * 加入注解的使用,不加入这个注解不生效
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

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

# 七、添加权限注解

此处只展示权限注解,其余注解请查询官方文档

注意

添加权限的注解必须被自定义拦截器拦截

否则会出现不调用自定义 CustomRealm中的doGetAuthorizationInf()方法的情况

代码示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    @GetMapping("/system/list")
    @RequiresPermissions(value = { "systemManage","system:list" },logical = Logical.OR)
    public ResultVO<Integer> resetPassword(String userId,@PathVariable Integer version) {
        return manageService.resetPassword(userId,version);
    }
  1. value :字符串数组,填写之前realm中权限注入的字符串即可
  2. logical:逻辑关系,数组中权限的逻辑关系,分为AND和OR两种,默认为AND
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-04-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Shiro 整合 Spring 第一次
4.1 在applicationContext-shiro.xml文件中配置 凭证匹配器
用户5927264
2019/07/31
3310
集成 SpringBoot 2.3.2 + Shiro 1.5.3 + jwt (无状态)
shiro 集成 jwt 需要禁用 session, 服务器就不用维护用户的状态, 做到无状态调用
北漂的我
2020/08/17
1.5K0
【Shiro】Springboot集成Shiro
(1)、ShiroDbRealmImpl继承ShiroDbRealm向上继承AuthorizingRealm,ShiroDbRealmImpl实例化时会创建密码匹配器HashedCredentialsMatcher实例,HashedCredentialsMatcher指定hash次数与方式,交于AuthenticatingRealm
陶然同学
2023/10/14
2950
【Shiro】Springboot集成Shiro
【Java专题_01】springboot+Shiro+Jwt整合方案
Apache Shiro :是一个强大且易用的Java安全框架,执行身份认证,授权,密码和会话管理,核心组件:Subject,SecurityManager和Realms;
夏之以寒
2024/03/04
7320
【Java专题_01】springboot+Shiro+Jwt整合方案
第三节,SpringBoot集成shrio,Redis缓存session与权限
https://gitee.com/DencyCheng/springboot-shrio/tree/dev/
DencyCheng
2018/12/12
2.7K0
Spring Boot中集成Shiro(十)
大家好,我是默语,一个专注于技术分享的博主。今天我们来探讨 Spring Boot中集成Shiro 的话题。在现代Web应用中,安全性是一个关键问题。Apache Shiro 是一个强大且灵活的Java安全框架,可以轻松地处理身份验证、授权、企业会话管理和加密。在这篇文章中,我们将深入探讨Shiro的核心组件,如何在Spring Boot项目中集成Shiro,包括依赖导入、数据库表数据初始化、自定义Realm以及Shiro配置。通过这篇文章,您将掌握Spring Boot与Shiro集成的关键技术,提升应用的安全性。让我们开始吧!💪
默 语
2024/11/20
3250
shiro实战之改造成token格式的无状态restful api
通过调用context.setSessionCreationEnabled(false)表示不创建会话;如果之后调用Subject.getSession()将抛出DisabledSessionException异常。
山行AI
2019/08/13
5.5K2
Spring Boot + Shiro整合
创建一个SpringBoot项目整合MyBatis,Thymeleaf,SpringMVC等并创建相关的配置文件和Service逻辑
Java鱼头
2022/12/01
3681
Springboot集成Shiro(前后端分离)
shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证、用户授权。
Ant丶
2022/03/01
3.5K0
Springboot集成Shiro(前后端分离)
Springboot整合Shiro之授权
Shiro是我们常用的一个权限管理框架,本文的重点是来介绍下在SpringBoot环境下我们怎么来使用Shiro。
Java帮帮
2019/12/13
6930
Springboot整合Shiro之授权
springboot整合shiro实现认证​
3.创建JwtDefaultSubjectFactory,来实现不保存session
java后端指南
2021/05/13
7490
springboot整合shiro实现认证​
shiro中改造成restful无状态服务的DisabledSessionException问题分析与解决
运行后,在调用 subject.login(token)方法时报错,报错信息如下:
山行AI
2019/12/02
2.5K0
springboot整合shiro实现权限控制
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。上个月写了一个在线教育的项目用到了shiro权限控制,这几天又复盘了一下,对其进行了深入探究,来总结一下。
jiankang666
2022/05/12
4570
springboot整合shiro实现权限控制
Spring Boot + Spring Cloud 实现权限管理系统 后端篇(十一):集成 Shiro 框架
Authentication(认证), Authorization(授权), Session Management(会话管理), Cryptography(加密)被 Shiro 框架的开发团队称之为应用安全的四大基石。
朝雨忆轻尘
2019/06/18
1.6K0
SpringBoot中关于Shiro权限管理的整合使用
​1.加入Shiro的依赖包,实现自己的Realm类(通过继承AuthorizingRealm类);​
张哥编程
2024/12/19
1990
springboot shiro权限管理「建议收藏」
大家好,我是架构君,一个会写代码吟诗的架构师。今天说一说springboot shiro权限管理「建议收藏」,希望能够帮助大家进步!!!
Java架构师必看
2022/08/14
9610
springboot shiro权限管理「建议收藏」
Springboot整合Shiro之认证
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
用户4919348
2019/12/02
5000
Springboot整合Shiro之认证
SpringBoot——整合Shiro完成登录检验
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/106450.html原文链接:https://javaforall.cn
全栈程序员站长
2022/08/04
3430
从零开始做网站7-整合shiro+jwt实现用户认证和授权
上一篇用shiro来登入存在用户认证的问题,而又不想用cookie session,所以决定使用jwt来做用户认证
sunonzj
2022/06/21
1.2K0
从零开始做网站7-整合shiro+jwt实现用户认证和授权
SpringBoot整合Shiro_HelloWorld
Apache Shiro是一个功能强大、灵活的,开源的安全框架。它可以干净利落地处理身份验证、授权、企业会话管理和加密。
CBeann
2023/12/25
2260
SpringBoot整合Shiro_HelloWorld
推荐阅读
相关推荐
Shiro 整合 Spring 第一次
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验