首页 > 其他分享 >测试平台开发(一)鉴权模块7 Shiro基于JWT的认证

测试平台开发(一)鉴权模块7 Shiro基于JWT的认证

时间:2024-11-09 21:45:52浏览次数:3  
标签:return JWT public token new response 鉴权 Shiro

Shiro简介

Apache Shiro 是一个强大且易用的 Java 安全框架,主要用于身份认证、授权、加密和会话管理。它的设计目标是简化安全性的实现,使开发者能够更专注于业务逻辑。以下是 Shiro 的主要作用和功能:

1. 身份认证(Authentication)
用户登录:Shiro 提供了简单而强大的 API 来处理用户登录。你可以通过 Subject 对象调用 login 方法来验证用户的身份。
多方式认证:Shiro 支持多种形式的认证,包括用户名/密码、证书、OpenID 等。
自定义认证:你可以通过实现自定义的 Realm 来扩展 Shiro 的认证功能,以适应不同的认证需求。
2. 授权(Authorization)
权限管理:Shiro 提供了细粒度的权限管理功能,可以基于角色、权限字符串等方式进行授权。
访问控制:Shiro 支持 URL 访问控制、方法级别的访问控制等,可以灵活地控制用户对资源的访问。
注解支持:Shiro 提供了注解支持,如 @RequiresRoles、@RequiresPermissions 等,可以在代码中方便地进行权限检查。
3. 会话管理(Session Management)
无状态会话:Shiro 支持无状态会话,适用于分布式系统和无状态应用,如使用 JWT 进行身份验证。
会话集群:Shiro 可以将会话信息存储在集群中,确保会话的一致性和可用性。
会话监听:Shiro 提供了会话监听器,可以监控会话的创建、更新和销毁事件。
4. 加密(Cryptography)
密码加密:Shiro 提供了多种密码加密算法,如 SHA-256、MD5 等,确保用户密码的安全。
对称加密:Shiro 支持对称加密算法,如 AES,用于保护敏感数据。
哈希和盐:Shiro 提供了哈希和盐的功能,可以增强密码的安全性。
5. Web 支持
过滤器:Shiro 提供了 ShiroFilter,可以轻松地集成到 Web 应用中,进行 URL 访问控制。
标签库:Shiro 提供了 JSP 标签库,可以在页面中进行权限检查和显示不同的内容

1、引入shiro的maven依赖包

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.2.5</version>
            <scope>compile</scope>
            <exclusions>
                <exclusion>
                    <artifactId>commons-beanutils</artifactId>
                    <groupId>commons-beanutils</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.2.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.2.5</version>
        </dependency>

2、使用token自动登录

1)使用身份验证过滤器BasicHttpAuthenticationFilter,验证用户是否为登录状态,接口调用时生效;

      如果为否执行登录,登录失败会返回401错误。

public class JWTFilter extends BasicHttpAuthenticationFilter {
    /**
     * 检查用户是否登录
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        // 验证请求头中是否存在Authorization字段,不存在则返回false
        String header = req.getHeader(SecurityUtils.REQUEST_AUTH_HEADER);
        return header != null;
    }
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String header = req.getHeader(SecurityUtils.REQUEST_AUTH_HEADER);
        // 执行登录时需要传入token,因此要传入一个AuthenticationToken对象
        JWTTokenDto jwtTokenDto = new JWTTokenDto();
        jwtTokenDto.setToken(header);
        getSubject(request,response).login(jwtTokenDto);
        return true;
    }
    /**
     * 是否允许登录
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        boolean loginAttempt = isLoginAttempt(request, response);
        if (loginAttempt){
            executeLogin(request,response);
            return true;
        }
        // 如果未登录状态,则返回401错误
        response401(response);
        return true;
    }
    private Boolean response401(ServletResponse response){
        HttpServletResponse res = (HttpServletResponse) response;
        res.setStatus(HttpStatus.UNAUTHORIZED.value());
        res.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = null;
        try {
            out = res.getWriter();
            out.append(JSON.toJSONString(ResponseFactory.getError(ResponseCode.please_login)));
            out.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            if (out != null){
                out.close();
            }
        }
        return false;
    }
}

2)登录的方式是使用token进行登录,因此需要定义token类

public class JWTTokenDto implements AuthenticationToken {
    private String token;

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

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

3、配置shiro拦截规则

shiro规则使用@Configuration注解,作为组件在启动时配置,加载以下三个部件:

①shiroFilter定义的URI过滤规则

②securityManager定义的安全会话规则

③指定JWTRealm()实现的鉴权规则

@Configuration
public class ShiroConfig {
    /**
     * 定义权限,启动时加载shiro拦截器
     * @Qualifier注解用来指定需要的bean
     * @param manager
     * @return
     */
    @Bean(name="shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager")SecurityManager manager){
        // 定义拦截规则,此处加载了https的规则、以及JWT使用token进行验证的规则
        Map<String, Filter> filterMap = new HashMap<>();
        // https的规则,定义过滤器名称为ssl
        filterMap.put("ssl",new SslFilter());
        // 配置规则名称为jwt,定义过滤器名称为jwt
        filterMap.put("jwt",new JWTFilter());

        // 设定不需要拦截的规则,加入到这个规则的URI不会拦截
        LinkedHashMap<String,String> filterRules = new LinkedHashMap<>();
        // 匹配规则:模糊匹配用**,anon代表匿名访问,用anon标记规则不需要身份验证
        filterRules.put("/swagger**","anon");
        filterRules.put("/swagger-ui/**","anon");
        // 把登录接口驾到过滤规则中,不然登录也会提示401
        filterRules.put("/api/auth/tokens","anon");
        filterRules.put("/api/auth/regist","anon");
        filterRules.put("/api/auth/401","anon");
        // 设置需要拦截的规则,所有接口都会被拦截,拦截规则为jwt规则,即前面定义的JWTFilter()
        filterRules.put("/**","jwt");

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setFilters(filterMap);
        // 设置安全事务管理器,使用@Qualifier("securityManager")进行了指定
        shiroFilterFactoryBean.setSecurityManager(manager);
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterRules);
        // 设置login地址
        shiroFilterFactoryBean.setLoginUrl("/course/login");
        // 设置没有登录的地址
        shiroFilterFactoryBean.setUnauthorizedUrl("/course/401");
        return shiroFilterFactoryBean;
    }

    // 配置核心安全事务管理器
    @Bean(name = "securityManager")
    public SecurityManager securityManager(){
        // 禁用会话存储,意味着shiro不会把用户的会话信息存储在内存中
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        // 设置SubjectDAO禁用会话存储,Subject代表当前安全上下文,DAO代码访问安全信息的接口
        DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
        defaultSubjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setSubjectDAO(defaultSubjectDAO);
        // jwtRealm用于处理JWT的认证和授权,setRealm后会让shiro用该realm进行身份认证
        manager.setRealm(jwtRealm());
        return manager;
    }

    @Bean(name = "jwtRealm")
    public JWTRealm jwtRealm(){
        JWTRealm jwtRealm = new JWTRealm();
        return jwtRealm;
    }
}

4、配置AuthorizingRealm鉴权规则

AuthorizingRealm中的doGetAuthenticationInfo,主要用途是实现具体的身份验证逻辑。当用户尝试登录或访问需要身份验证的资源时,框架会调用这个方法来验证用户的身份。

supports则用来定义doGetAuthenticationInfo是否执行,supports返回true则执行,否则不执行。

public class JWTRealm extends AuthorizingRealm {
    @Autowired
    private UserServiceImp userService;

    @Override
    public boolean supports(AuthenticationToken token) {
        // token是否属于JWTTokenDto,属于则启用下面的鉴权规则
        return token instanceof JWTTokenDto;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 从uri中取出token
        String token = (String)authenticationToken.getCredentials();
        // 从token中取出username
        String userAccount = JWTUtils.getUserAccount(token);
        // 从数据库查询该用户名是否存在
        UserEntity user = userService.getUserByAccount(userAccount);
        if (user == null){
            throw new ServiceException(ResponseCode.please_login);
        }
        // 判断用户状态是否已经停用
        if ("-1".equals(String.valueOf(user.getUserStatus()))){
            throw new ServiceException(ExceptionCodeEnum.user_disable);
        }
        // 判断用户名密码错误
        if (!JWTUtils.verify(token,userAccount,user.getPassword())){
            throw new ServiceException(ExceptionCodeEnum.user_password_error);
        }

        SimpleAuthenticationInfo jwtRealm = new SimpleAuthenticationInfo(token, token, "JwtRealm");
        return jwtRealm;
    }
}

代码中调用了JWTUtils.getUserAccount、JWTUtils.verify,代码如下:

    public static String getUserAccount(String token){
        try {
            // 解码token获得用户名
            DecodedJWT decode = JWT.decode(token);
            // claim是token中的声明信息
            return decode.getClaim(SecurityUtils.ACCOUNT).asString();
        } catch (JWTDecodeException exception){
            // 解析失败,返回空
            return null;
        }
    }

    public static boolean verify(String token,String account,String pwd){
        try{
            // 使用HMAC256算法创建一个加密算法对象,密钥为传入的密码pwd。
            Algorithm algorithm = Algorithm.HMAC256(pwd);
            // 使用当前用户名和算法对象,构建一个JWT验证器,用来验证传入的token
            JWTVerifier jwtVerifier = JWT.require(algorithm).withClaim(SecurityUtils.ACCOUNT, account).build();
            // verify(token)方法会检查令牌的签名、过期时间以及声明(Claim)
            // 如果一切正常,会返回一个DecodedJWT对象,如果验证失败,会抛出相应的异常。
            DecodedJWT verify = jwtVerifier.verify(token);
            return true;
        }catch (Exception e){
            return false;
        }
    }

标签:return,JWT,public,token,new,response,鉴权,Shiro
From: https://blog.csdn.net/weixin_39477393/article/details/143643808

相关文章

  • 测试平台开发(一)鉴权模块5 用户登录与异常拦截
    一、定义异常类型使用以下代码定义了ServiceException,集成了RuntimException,用于在系统中捕捉和返回异常信息。@Data@Slf4jpublicclassServiceExceptionextendsRuntimeException{privatestaticfinallongserialVersionUID=12345678123456L;privateInt......
  • CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro
    说明此文章为转发的,方便日后查看。系统演示环境http://www.cc-admin.top/#/home简介CC-ADMIN前端简介现在市面的上后台管理系统很多,不差你这一个,为啥又来个轮子?答:材料不一样。本轮子的选材是在考察过antv、element之后选择了quasar,前两个很优秀,尤其是antv的外观我特......
  • 适合才最美:Shiro安全框架使用心得
    大家好,我是V哥。ApacheShiro是一个强大且灵活的Java安全框架,专注于提供认证、授权、会话管理和加密功能。它常用于保护Java应用的访问控制,特别是在Web应用中。相比于SpringSecurity,Shiro的设计更简洁,适合轻量级应用,并且在许多方面具有更好的易用性和扩展性,今......
  • 腿夹腿,带你用react撸后台,系列三(布局和鉴权篇)
    Github地址|文档|在线预览|主题版在线预览react-antd-console是一个后台管理系统的前端解决方案,封装了后台管理系统必要功能(如登录、鉴权、菜单、面包屑、标签页等),帮助开发人员专注于业务快速开发。项目基于React18、Antdesign5、Vite和TypeScript等新......
  • 【鉴权】OAuth 2.0: 高度灵活与安全的身份认证框架
    目录引言一、OAuth2.0的核心概念1.1资源拥有者(ResourceOwner)1.2客户端(Client)1.3授权服务器(AuthorizationServer)1.4资源服务器(ResourceServer)1.5OAuth2.0体系架构图二、OAuth2.0授权流程2.1OAuth2.0授权流程概述2.2常见的授权模式2.2.1授权码模式(Autho......
  • 【鉴权】深入理解 OAuth 2.0 授权流程
    目录引言OAuth2.0授权流程概览1.用户访问客户端应用2.客户端请求授权码3.授权服务器展示授权页面4.用户同意授权5.客户端使用授权码换取访问令牌6.授权服务器返回访问令牌7.客户端使用令牌访问资源8.资源服务器返回受保护资源9.客户端展示数据10.访问令牌过......
  • JWT工具类
    JWT工具类一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。前两部分需要经过Base64编码,后一部分通过前两部分Base64编码后再加密而成。具体内容可参考官方文档。头部(Header)头部用于描述关于该JWT的最基本的信息载荷(playload)载荷就是存放有效信息的地方......
  • JWT令牌——从入门到精通
    一、跨域认证的问题互联网服务离不开用户认证。一般流程是下面这样。1、用户向服务器发送用户名和密码。2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。3、服务器向用户返回一个session_id,写入用户的Cookie。4、用户随后的每一......
  • Dify 中的 Bearer Token 与 API-Key 鉴权方式
    本文使用Difyv0.10.2版本,在Dify中包括BearerToken与API-Key鉴权这2种方式。console(URL前缀/console/api)和web(URL前缀/api)蓝图使用的是BearerToken鉴权方式,而service_api(URL前缀/v1)蓝图使用的是API-Key鉴权方式。console蓝图通过login_required装饰......
  • 实现用户认证功能:Vue与JWT
    实现用户认证功能:Vue与JWT在现代前端开发中,用户认证是一个极为重要的功能,常常涉及到用户数据的安全管理。本文将为您展示如何使用Vue.js与JWT(JSONWebToken)实现用户认证功能。我们将通过一个简单的示例,展示如何在Vue3中使用CompositionAPI(setup语法糖)来进行用户......