Spring Oauth2.0 自定义认证模式

这篇具有很好参考价值的文章主要介绍了Spring Oauth2.0 自定义认证模式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


在特定场景下,可能Oauth2自带的4种认证模式可能满足不了我们日常的使用,那么今天就为大家带来Oauth2自定义认证模式。

1. 什么是OAuth2.0

知道你们肯定没耐心读完(总结一句话就是授权用的),有耐心的可以读完下面的内容介绍:

首先呢在这之前我们要搞清楚什么是Oauth , OAuth 是一个开放标准,该标准允许用户让第三方应用访问该用户在某一网站上存储的私密资源(如头像、照片、视频等),而在这个过程中无需将用户名和密码提供给第三方应用。实现这一功能是通过提供一个令牌(token),而不是用户名和密码来访问他们存放在特定服务提供者的数据。

采用令牌(token)的方式可以让用户灵活的对第三方应用授权或者收回权限。

OAuth2 是 OAuth 协议的下一版本,但不向下兼容 OAuth 1.0。

传统的 Web 开发登录认证一般都是基于 session 的,但是在前后端分离的架构中继续使用 session 就会有许多不便,因为移动端(Android、iOS、微信小程序等)要么不支持 cookie(微信小程序),要么使用非常不便,对于这些问题,使用 OAuth2 认证都能解决。

对于大家而言,我们在互联网应用中最常见的 OAuth2 应该就是各种第三方登录了,例如 QQ 授权登录、微信授权登录、微博授权登录、GitHub 授权登录等等。

2. 默认的四种验证模式

授权码模式:常见的第三方平台登录功能基本都是使用这种模式。

简化模式:简化模式是不需要客户端服务器参与,直接在浏览器中向授权服务器申请令牌(token),一般如果网站是纯静态页面则可以采用这种方式。

密码模式:密码模式是用户把用户名密码直接告诉客户端,客户端使用说这些信息向授权服务器申请令牌(token)。这需要用户对客户端高度信任,例如客户端应用和服务提供商就是同一家公司,自己做前后端分离登录就可以采用这种模式。

客户端模式:客户端模式是指客户端使用自己的名义而不是用户的名义向服务提供者申请授权,严格来说,客户端模式并不能算作 OAuth 协议要解决的问题的一种解决方案,但是,对于开发者而言,在一些前后端分离应用或者为移动端提供的认证授权服务器上使用这种模式还是非常方便的。

3. 上代码

好了有什么具体不懂的可以自行百度,费话不多说上代码

== 首先我们需要创建一个类,它继承抽象身份验证令牌类(AbstractAuthenticationToken)==

// 我拿一个微信授权为例子
public class WechatAuthenticationToken extends AbstractAuthenticationToken {
// 这里呢相当于实体类,用于存放数据,里面的字段可以自己定义,我这里仅供参考,具体以实际业务需要为准
    private static final long serialVersionUID = 550L;
   
  	//我也不知道这个是干嘛用的,反正必须要有
    private final Object principal;
    @Getter
    private String encryptedData;
    @Getter
    private String iv;
    @Getter
    private String pEncryptedData;
    @Getter
    private String piv;



    /**
     * 账号校验之前的token构造方法
     *
     * @param principal
     */
    public WechatAuthenticationToken(Object principal, String encryptedData,String iv,String pEncryptedData, String piv) {
        super(null);
        this.principal = principal;
        this.encryptedData = encryptedData;
        this.iv=iv;
        this.pEncryptedData = pEncryptedData;
        this.piv = piv;
        setAuthenticated(false);
    }

    /**
     * 账号校验成功之后的token构造方法
     *
     * @param principal
     * @param authorities
     */
    public WechatAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        super.setAuthenticated(true);
    }

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

    @Override
    public Object getPrincipal() {
        return this.principal;
    }

    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        Assert.isTrue(!isAuthenticated, "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        super.setAuthenticated(false);
    }

    public void eraseCredentials() {
        super.eraseCredentials();
    }
}

这里我们创建一个类继承令牌授权器(AbstractTokenGranter)

public class WechatTokenGranter extends AbstractTokenGranter {

    /**
     * 声明授权者 WechatTokenGranter 支持授权模式 wechat
     * 根据接口传值 grant_type = wechat 的值匹配到此授权者
     * 匹配逻辑详见下面的两个方法
     */
     // 这里就是授权模式名
    private static final String GRANT_TYPE = "wechat";
    private final AuthenticationManager authenticationManager;
		
	// 这里创建一个构造函数,配置的时候会用到	
    public WechatTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager) {
        super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
        this.authenticationManager = authenticationManager;
    }
	
	/*
	* 重写方法
	* @param client   客户端详细信息
     *@param tokenRequest  请求参数
	* */
    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
	// 这里将认证时的请求参数拿到
        Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
        String code = parameters.get("code");
        String encryptedData = parameters.get("encryptedData");
        String iv = parameters.get("iv");
        String pEncryptedData = parameters.get("pEncryptedData");
        String piv = parameters.get("piv");

        // 移除后续无用参数
        parameters.remove("code");
        parameters.remove("encryptedData");
        parameters.remove("iv");
        parameters.remove("pEncryptedData");
        parameters.remove("piv");


		// 这里是我们之前创建的授权前的构造函数,把参数传进去
        Authentication userAuth = new WechatAuthenticationToken(code, encryptedData,iv,pEncryptedData,piv); // 未认证状态
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
		
		// 对参数进行认证
        try {
            userAuth = this.authenticationManager.authenticate(userAuth); // 认证中
        } catch (Exception e) {
            throw new InvalidGrantException(e.getMessage());
        }
			
		// 判断时候认证成功
        if (userAuth != null && userAuth.isAuthenticated()) { // 认证成功
            OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
            return new OAuth2Authentication(storedOAuth2Request, userAuth);
        } else { // 认证失败
            throw new InvalidGrantException("Could not authenticate code: " + code);
        }
    }
}

== 这里创建一个实体类实现AuthenticationProvider接口==

@Data
public class WechatAuthenticationProvider implements AuthenticationProvider {

	// 这个事Security的用户是如何验证的接口,具体需要去实现
    private UserDetailsService userDetailsService;
    // 这个包是Github上的binarywang,调用微信接口的SDK
    private WxMaService wxMaService;
    // 由于我们的程序是Cloud,这里是跨服务调用的,可根据你们自己的业务需求来
    private MemberFeignClient memberFeignClient;

    /**
     * 微信认证
     *
     * @param authentication
     * @return
     * @throws AuthenticationException
     */
    @SneakyThrows
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        WechatAuthenticationToken authenticationToken = (WechatAuthenticationToken) authentication;
        String code = (String) authenticationToken.getPrincipal();
		// 以下是自定义验证
        WxMaJscode2SessionResult sessionInfo =  wxMaService.getUserService().getSessionInfo(code);
        String openid = sessionInfo.getOpenid();
        Result<MemberAuthDTO> memberAuthResult = memberFeignClient.loadUserByOpenId(openid);
        // 微信用户不存在,注册成为新会员
        if (memberAuthResult != null && ResultCode.USER_NOT_EXIST.getCode().equals(memberAuthResult.getCode())) {

            String sessionKey = sessionInfo.getSessionKey();
            String encryptedData = authenticationToken.getEncryptedData();
            String iv = authenticationToken.getIv();

            String pEncryptedData = authenticationToken.getPEncryptedData();
            String piv = authenticationToken.getPiv();

            // 解密 encryptedData 获取用户信息
            WxMaUserInfo userInfo = wxMaService.getUserService().getUserInfo(sessionKey, encryptedData, iv);


            WxMaPhoneNumberInfo phoneNoInfo = wxMaService.getUserService().getPhoneNoInfo(sessionKey, pEncryptedData, piv);

            MemberDTO memberDTO = new MemberDTO();
            BeanUtil.copyProperties(userInfo, memberDTO);
            memberDTO.setOpenid(openid);
            memberDTO.setMobile(phoneNoInfo.getPhoneNumber());
            memberFeignClient.addMember(memberDTO);
        }
        UserDetails userDetails = ((MemberUserDetailsServiceImpl) userDetailsService).loadUserByOpenId(openid);
        WechatAuthenticationToken result = new WechatAuthenticationToken(userDetails, new HashSet<>());
        result.setDetails(authentication.getDetails());
        return result;
    }


    @Override
    public boolean supports(Class<?> authentication) {
        return WechatAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

最重要的一步来了,这里我们需要重写授权服务器配置适配器(AuthorizationServerConfigurerAdapter),并且初始化一些配置

@Configuration
@EnableAuthorizationServer
@RequiredArgsConstructor
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private final AuthenticationManager authenticationManager;
    private final SysUserDetailsServiceImpl sysUserDetailsService;
    private final MemberUserDetailsServiceImpl memberUserDetailsService;
    private final StringRedisTemplate stringRedisTemplate;
    private final DataSource dataSource;

    private final TokenEnhancer tokenEnhancer;

    /**
     * OAuth2客户端
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    	// 这里有两种模式,一种是放在内存,还有一种是放在数据库,默认内存,这里我放在数据库,所以需要重写
        clients.withClientDetails(jdbcClientDetailsService());
    }

    /**
     * 配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        // Token增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
        tokenEnhancers.add(tokenEnhancer);
        tokenEnhancers.add(jwtAccessTokenConverter());
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);

        //token存储模式设定 默认为InMemoryTokenStore模式存储到内存中
        endpoints.tokenStore(jwtTokenStore());

        // 获取原有默认授权模式(授权码模式、密码模式、客户端模式、简化模式)的授权者
        List<TokenGranter> granterList = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));

        // 添加验证码授权模式授权者
        granterList.add(new CaptchaTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
                endpoints.getOAuth2RequestFactory(), authenticationManager, stringRedisTemplate
        ));

        // 添加手机短信验证码授权模式的授权者
        granterList.add(new SmsCodeTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
                endpoints.getOAuth2RequestFactory(), authenticationManager
        ));

        // 添加微信授权模式的授权者 ********在这里添加我们刚才自定义的东西*******
        granterList.add(new WechatTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
                endpoints.getOAuth2RequestFactory(), authenticationManager
        ));

        CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList);
        endpoints
                .authenticationManager(authenticationManager)
                .accessTokenConverter(jwtAccessTokenConverter())
                .tokenEnhancer(tokenEnhancerChain)
                .tokenGranter(compositeTokenGranter)
                .tokenServices(tokenServices(endpoints))
        ;
    }


    /**
     * 客户端信息来源
     */
    @Bean
    public JdbcClientDetailsService jdbcClientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }


    /**
     * jwt token存储模式
     */
    @Bean
    public TokenStore jwtTokenStore() {
        return new JdbcTokenStore(dataSource);
    }


    /**
     * 授权信息保存策略
     */
    @Bean
    public ApprovalStore approvalStore(){
        return new JdbcApprovalStore(dataSource);
    }


    /**
     * 授权码模式数据来源
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        return new JdbcAuthorizationCodeServices(dataSource);
    }



    public DefaultTokenServices tokenServices(AuthorizationServerEndpointsConfigurer endpoints) {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
        tokenEnhancers.add(tokenEnhancer);
        tokenEnhancers.add(jwtAccessTokenConverter());
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);

        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(jdbcClientDetailsService());
        tokenServices.setTokenEnhancer(tokenEnhancerChain);

        // 多用户体系下,刷新token再次认证客户端ID和 UserDetailService 的映射Map
        Map<String, UserDetailsService> clientUserDetailsServiceMap = new HashMap<>();
        clientUserDetailsServiceMap.put(SecurityConstants.ADMIN_CLIENT_ID, sysUserDetailsService); // 系统管理客户端
        clientUserDetailsServiceMap.put(SecurityConstants.APP_CLIENT_ID, memberUserDetailsService); // Android、IOS、H5 移动客户端
        clientUserDetailsServiceMap.put(SecurityConstants.WEAPP_CLIENT_ID, memberUserDetailsService); // 微信小程序客户端

        // 刷新token模式下,重写预认证提供者替换其AuthenticationManager,可自定义根据客户端ID和认证方式区分用户体系获取认证用户信息
        PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
        provider.setPreAuthenticatedUserDetailsService(new PreAuthenticatedUserDetailsService<>(clientUserDetailsServiceMap));
        tokenServices.setAuthenticationManager(new ProviderManager(Arrays.asList(provider)));

        /**
         * refresh_token有两种使用方式:重复使用(true)、非重复使用(false),默认为true
         *  1 重复使用:access_token过期刷新时, refresh_token过期时间未改变,仍以初次生成的时间为准
         *  2 非重复使用:access_token过期刷新时, refresh_token过期时间延续,在refresh_token有效期内刷新便永不失效达到无需再次登录的目的
         */
        tokenServices.setReuseRefreshToken(true);
        return tokenServices;

    }

    /**
     * 使用非对称加密算法对token签名
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setKeyPair(keyPair());
        return converter;
    }

    /**
     * 密钥库中获取密钥对(公钥+私钥)
     */
    @Bean
    public KeyPair keyPair() {
        KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), "123456".toCharArray());
        KeyPair keyPair = factory.getKeyPair("jwt", "123456".toCharArray());
        return keyPair;
    }

    /**
     * 自定义认证异常响应数据
     */
    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return (request, response, e) -> {
            response.setStatus(HttpStatus.HTTP_OK);
            response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Cache-Control", "no-cache");
            Result result = Result.failed(ResultCode.CLIENT_AUTHENTICATION_FAILED);
            response.getWriter().print(JSONUtil.toJsonStr(result));
            response.getWriter().flush();
        };
    }
}

好了以上就是自定义验证模式的全部内容,有什么不懂的话可以评论下问我,我也会一 一解答,谢谢各位文章来源地址https://www.toymoban.com/news/detail-594810.html

到了这里,关于Spring Oauth2.0 自定义认证模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 微服务统一认证方案Spring Cloud OAuth2+JWT

    目录 前言 一、微服务架构下的统一认证场景 二、微服务架构下的统一认证思路 1.基于Session的认证方式 2.基于token的认证方式(主流) 三、OAuth2开放授权协议/标准 1.OAuth2介绍 2.OAuth2协议角色和流程 3.什么情况下需要使用OAuth2? 4.OAuth2的颁发Token授权方式 四、Spring Cloud OAuth2+JWT实现

    2023年04月09日
    浏览(71)
  • Spring Boot OAuth2 认证服务器搭建及授权码认证演示

    本篇使用JDK版本是1.8,需要搭建一个OAuth 2.0的认证服务器,用于实现各个系统的单点登录。 这里选择Spring Boot+Spring Security + Spring Authorization Server 实现,具体的版本选择如下: Spirng Boot 2.7.14 , Spring Boot 目前的最新版本是 3.1.2,在官方的介绍中, Spring Boot 3.x 需要JDK 17及以上的

    2024年02月15日
    浏览(50)
  • SpringCloud整合spring security+ oauth2+Redis实现认证授权

    在微服务构建中,我们一般用一个父工程来通知管理依赖的各种版本号信息。父工程pom文件如下: 在SpringCloud微服务体系中服务注册中心是一个必要的存在,通过注册中心提供服务的注册和发现。具体细节可以查看我之前的博客,这里不再赘述。我们开始构建一个eureka注册中

    2024年02月06日
    浏览(58)
  • Spring Cloud Gateway + Oauth2 实现统一认证和鉴权!

    micro-oauth2-gateway:网关服务,负责请求转发和鉴权功能,整合Spring Security+Oauth2; micro-oauth2-auth:Oauth2认证服务,负责对登录用户进行认证,整合Spring Security+Oauth2; micro-oauth2-api:受保护的API服务,用户鉴权通过后可以访问该服务,不整合Spring Security+Oauth2。 我们首先来搭建认

    2024年01月16日
    浏览(62)
  • Spring Cloud Gateway 整合OAuth2.0 实现统一认证授权

    Spring Cloud Gateway 整合OAuth2.0 实现统一认证授权 GateWay——向其他服务传递参数数据 https://blog.csdn.net/qq_38322527/article/details/126530849 @EnableAuthorizationServer Oauth2ServerConfig 验证签名 网关服务需要RSA的公钥来验证签名是否合法,所以认证服务需要有个接口把公钥暴露出来 接下来搭建网

    2024年02月13日
    浏览(43)
  • 授权码 + PKCE 模式|OIDC & OAuth2.0 认证协议最佳实践系列【03】

    ​ 在上一篇文章中,我们介绍了 OIDC 授权码模式(点击下方链接查看), 本次我们将重点围绕 授权码 + PKCE 模式(Authorization Code With PKCE)进行介绍 ,从而让你的系统快速具备接入用户认证的标准体系。 OIDC OAuth2.0 认证协议最佳实践系列 02 - 授权码模式(Authorization Code)接

    2024年02月01日
    浏览(93)
  • Spring Gateway+Security+OAuth2+RBAC 实现SSO统一认证平台

    背景:新项目准备用SSO来整合之前多个项目的登录和权限,同时引入网关来做后续的服务限流之类的操作,所以搭建了下面这个系统雏形。 : Spring Gateway, Spring Security, JWT, OAuth2, Nacos, Redis, Danymic datasource, Javax, thymeleaf 如果对上面这些技术感兴趣,可以继续往下阅读 如

    2024年02月13日
    浏览(56)
  • 自定义OAuth2组件实现对授权码登录模式的封装

    所谓OAuth2其实就是Open Authorization,即开放授权,是一种授权机制或者说是一种协议。 OAuth2允许用户授权第三方应用访问其存储在开放平台(授权服务器)中的数据而不需要提供密码 。授权服务器根据OAuth2协议标准制订一套授权的API,第三方网站接入开放平台之后即可通过其

    2024年02月05日
    浏览(38)
  • 基于Spring OAuth2 之客户端凭证模式演示

    客户端凭证模式(Client Credentials Grant) :适用于客户端访问自己的资源的情况,而不是代表用户访问资源。 客户端应用程序使用其自身的凭证(即客户端ID和客户端密钥)直接向身份验证服务器进行身份验证,并获取访问令牌。此授权方式不涉及用户,因为它只允许客户端访

    2024年02月14日
    浏览(38)
  • Spring Authorization Server入门 (八) Spring Boot引入Security OAuth2 Client对接认证服务

    在之前的文章中实现了一个认证服务,并且添加了一些自定义的内容,现在暂时没想到认证服务的新内容,本篇文章就先写一下客户端对接的吧,水一篇。 当用户通过客户端去访问一个受限的资源时,客户端会检测是否有登录信息,没有登录信息会重定向至认证服务器去请求

    2024年02月21日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包