SpringBoot整合OAuth2(授权码模式 简单版)

  • maven依赖
		<!---基础依赖【自行添加 mysql,mybatis,web的依赖】-->
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.6.RELEASE</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
            <version>1.1.0.RELEASE</version>
        </dependency>
        
  • yml
spring:
  application:
    name: mock_demo
  redis:
    host: 127.0.0.1
    port: 6379
  datasource:
    url: jdbc:mysql://localhost:3306/user_db
    username: root
    password: 12345678
    driver-class-name: com.mysql.jdbc.Driver
server:
  port: 9898

  • bean [UserInfo数据库对象 LoginUserInfo 包装userDetails的对象]
package com.kyrie.oauth2.bean;

import lombok.Data;

import java.io.Serializable;

/**
 * @author:kyrie
 * @date:2024/3/14 13:36
 * @Description: user的bean对象
 **/
@Data
public class UserInfo implements Serializable {

    private static final long serialVersionUID = -8992966180137961891L;

    private Long userId;
    private String username;
    private String password;

}

package com.kyrie.oauth2.bean;

import lombok.AllArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

/**
 * @author:kyrie
 * @date:2024/3/14 13:37
 * @Description: 登录的user的bean对象
 **/
@AllArgsConstructor
public class LoginUserInfo implements UserDetails {

    private static final long serialVersionUID = -4823305657709121441L;

    /**
     * 数据库的user对象
     */
    private final UserInfo userInfo;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return userInfo.getPassword();
    }

    @Override
    public String getUsername() {
        return userInfo.getUsername();
    }

    public UserInfo getUserInfo() {
        return userInfo;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

}

  • mapper
package com.kyrie.oauth2.mapper;

import com.kyrie.oauth2.bean.UserInfo;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

/**
 * @author:kyrie
 * @date:2024/3/14 13:39
 * @Description: user的mapper
 **/
public interface UserInfoMapper {

    /**
     * 根据userName查询user信息
     *
     * @param userName 用户名
     * @return user对象
     */
    @Select("SELECT user_id userId,username,password FROM user WHERE username = #{userName} LIMIT 1")
    UserInfo selectUserByUserName(@Param("userName") String userName);

}

  • serviceImpl [实现security的UserDetailsService]
package com.kyrie.oauth2.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.kyrie.oauth2.bean.LoginUserInfo;
import com.kyrie.oauth2.bean.UserInfo;
import com.kyrie.oauth2.mapper.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

/**
 * @author:kyrie
 * @date:2024/3/14 13:39
 * @Description: 自己的登录实现
 **/
@Component
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    private UserInfoMapper userInfoMapper;

    /**
     * 自定义查询user信息 security会调用此方法验证user
     *
     * @param username 用户名
     * @return UserDetails的封装对象
     * @throws UsernameNotFoundException 人员信息不存在Exception
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        UserInfo user = userInfoMapper.selectUserByUserName(username);
        if (ObjectUtil.isNull(user)) {
            throw new UsernameNotFoundException(username + " 用户信息不存在");
        }

        return new LoginUserInfo(user);

    }

}

  • controller [随便写个controller测试一下]
package com.kyrie.oauth2.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author:kyrie
 * @date:2024/3/14 15:18
 * @Description: 测试controller
 **/
@RestController
@RequestMapping("/hello")
public class HelloController {

    @GetMapping("/demo")
    public String helloDemo() {
        return "helloDemo";
    }
    
}

  • config [认证服务器,资源服务器,security,jwt的一些配置]
package com.kyrie.oauth2.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author:kyrie
 * @date:2024/3/13:46
 * @Description: spring security配置
 **/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 注入密码加密对象
     *
     * @return 加密对象
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 注入认证管理器  用于手动验证用户 【解决无法直接注入AuthenticationManager】
     *
     * @return 管理对象
     * @throws Exception 异常
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * HTTP请求路径权限配置
     *
     * @param http HttpSecurity对象
     * @throws Exception 异常
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // 可以直接访问路径
        String[] whitUrlStrArray = {"/oauth/**", "/login/**", "/logout/**"};

        http.csrf().disable()
                .authorizeRequests()
                .antMatchers(whitUrlStrArray).permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().permitAll();

    }
}

package com.kyrie.oauth2.config;

import cn.hutool.core.util.ObjectUtil;
import com.kyrie.oauth2.bean.LoginUserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import java.util.HashMap;
import java.util.Map;

/**
 * @author:kyrie
 * @date:2024/3/14 13:58
 * @Description: jwt的配置 【原始的token是UUID不实用 所以改为jwt格式 如果不使用jwt 没必要写这个config】
 *               配置了该config 只会在原先的token基础上增强,不会替换原先的token,原先的token的字段改为了jti
 **/
@Configuration
public class JwtConfig {

    /**
     * redis的连接工厂
     */
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    /**
     * jwt的秘钥
     */
    private static final String JWT_SIGN_KEY = "demo_key";

    /**
     * 配置token的存储策略 TokenStore下面有很多实现类 这边直接用redis存一下
     *
     * @return TokenStore实例对象
     */
    @Bean
    public TokenStore redisTokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }

    /**
     * accessToken的转化器
     *
     * @return JwtAccessTokenConverter
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        // 配置jwt使用的密钥
        accessTokenConverter.setSigningKey(JWT_SIGN_KEY);
        return accessTokenConverter;
    }

    /**
     * 增加令牌的策略
     *
     * @return token的增强器
     */
    @Bean
    public TokenEnhancer tokenEnhancer() {
        return (oAuth2AccessToken, oAuth2Authentication) -> {
            Map<String, Object> info = new HashMap<>(2);
            info.put("demo", "demoEnhancer");
            LoginUserInfo user = (LoginUserInfo) oAuth2Authentication.getPrincipal();
            if (ObjectUtil.isNotNull(user)) {
                if (ObjectUtil.isNotNull(user.getUserInfo())) {
                    info.put("userId", user.getUserInfo().getUserId());
                }
            }
            ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info);
            return oAuth2AccessToken;
        };
    }

}

package com.kyrie.oauth2.config;

import com.kyrie.oauth2.service.impl.UserDetailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import java.util.ArrayList;
import java.util.List;

/**
 * @author:kyrie
 * @date:2024/3/14 14:13
 * @Description: 认证授权服务器配置
 * <p>
 * /oauth/authorize:授权端点
 * /oauth/token:令牌端点
 * /oauth/confirm_access:用户确认授权提交端点
 * /oauth/error:授权服务错误信息端点
 * /oauth/check_token:用于资源服务访问的令牌解析端点
 * /oauth/token_key:提供公有密匙的端点,如果你使用JWT令牌的话
 * </p>
 **/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailServiceImpl userDetailsService;
    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    @Autowired
    private TokenEnhancer tokenEnhancer;

    /**
     * 配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services) 【可以配置token增强】
     *
     * @param endpointsConfigurer AuthorizationServerEndpointsConfigurer
     * @throws Exception 异常
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpointsConfigurer) throws Exception {

        // jwt增强器
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();

        List<TokenEnhancer> delegates = new ArrayList<>(2);
        delegates.add(tokenEnhancer);
        delegates.add(jwtAccessTokenConverter);
        enhancerChain.setTokenEnhancers(delegates);

        endpointsConfigurer
                .authenticationManager(authenticationManager)
                // 自己的service实现类
                .userDetailsService(userDetailsService)
                // token的保存方式
                .tokenStore(tokenStore)
                // token转化器
                .accessTokenConverter(jwtAccessTokenConverter)
                // token增强
                .tokenEnhancer(enhancerChain);

    }

    /**
     * 配置客户端配置 【能够使用内存或JDBC方式实现获取已注册的客户端详情】
     *
     * @param clients ClientDetailsServiceConfigurer
     * @throws Exception 异常
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                // 配置client-id
                .withClient("demo")
                // 配置client-secret
                .secret(passwordEncoder.encode("123456"))
                // 配置相关token的有效期
                .accessTokenValiditySeconds(60 * 60 * 2)
                .refreshTokenValiditySeconds(60 * 60 * 24 * 2)
                // 限制某个client可以访问的资源服务 【由resource端验证】
                .resourceIds("resource1")
                // 配置redirect_uri,用于授权成功后跳转
                .redirectUris("http://www.baidu.com")
                // 配置申请的权限范围
                .scopes("all")
                // 配置grant_type,表示授权类型
                .authorizedGrantTypes("authorization_code", "refresh_token");

    }

    /**
     * 配置安全配置相关信息
     *
     * @param security AuthorizationServerSecurityConfigurer
     * @throws Exception 异常
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 使得/oauth/check_token接口可用
        security.allowFormAuthenticationForClients();
        security.checkTokenAccess("permitAll()");
    }

}

package com.kyrie.oauth2.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;

/**
 * @author:kyrie
 * @da2024/3/14 13:57
 * @Description: 资源服务器配置
 **/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    /**
     * 【当资源服务器和授权服务器 放在一起没必要远程去调用】
     *
     * RemoteTokenServices中 配置了access_token的校验地址、client_id、client_secret这三个信息,
     * 当用户来资源服务器请求资源时,会携带上一个access_token,通过这里的配置,就能够校验出token是否正确。
     *
     * @return ResourceServerTokenServices
     */
    @Bean
    public ResourceServerTokenServices tokenService() {
        // 使用远程服务请求授权服务器校验token,必须指定校验token 的url、client_id,client_secret
        RemoteTokenServices tokenServices = new RemoteTokenServices();

        tokenServices.setClientId("demo");
        tokenServices.setClientSecret("123456");
        tokenServices.setCheckTokenEndpointUrl("http://localhost:9898/oauth/check_token");

        return tokenServices;
    }

    /**
     * 配置资源服务器
     *
     * @param resources ResourceServerSecurityConfigurer
     * @throws Exception 异常
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources
                // 验证某个client能不能访问
                .resourceId("resource1")
                // 配置令牌服务
                .tokenServices(tokenService());
    }

    /**
     * 配置资源
     *
     * @param http HttpSecurity
     * @throws Exception 异常
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .requestMatchers()
                .antMatchers("/hello/**");

    }
}

  • 测试

  • 用postman请求获取token [先用Basic Auth 输入username 和 password 就是clientId和clientSecret,然后再body里面填写code,类型,回调地址]
    获取token
    获取token

  • 再看redis中是否已经存上了token [因为使用的redis的store]
    redis中的token

  • 用postman调接口
    成功

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值