oauth 2.0密码模式框架搭建(java)

硅谷探秘者 1789 0 0

oauth 2.0密码模式框架搭建(java)

项目源码下载地址:http://www.jiajiajia.club/file/info/8GG7iM/109

一、什么是oauth协议

        OAuth(开放授权)是一个开放标准。允许第三方网站在用户授权的前提下访问在用户在服务商那里存储的各种信息。而这种授权无需将用户提供用户名和密码提供给该第三方网站。OAuth允许用户提供一个令牌给第三方网站,一个令牌对应一个特定的第三方网站,同时该令牌只能在特定的时间内访问特定的资源。

二、oauth2.0密码模式的授权流程

        密码模式相比较于授权码模式来说更为简单,单不是特别的安全,一般用于信任的第三方应用授权。流程如下图。

一般就分为两个步骤

  1. 第三方应用携带用户名和密码请求认证服务器,认证服务器判断用户信息无误后返回给第三方应用一个token。
  2. 第三方应用获取到token后就可以携带着token去请求资源服务器,当然资源服务器也会去认证服务器去判断你携带的token是否合法。如果合法,才会返回给你需要的资源 。

三、需要的基本pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.5.10.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
  </parent>
    <groupId>com.curise.microservice</groupId>
    <version>1.0-SNAPSHOT</version>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>password_server</artifactId>
    <description>OAuth2.0密码模式</description>
	    <dependencies>
	        <dependency>
	            <groupId>org.springframework.boot</groupId>
	            <artifactId>spring-boot-starter-security</artifactId>
	        </dependency>
	        <dependency>
	            <groupId>org.springframework.boot</groupId>
	            <artifactId>spring-boot-starter-web</artifactId>
	        </dependency>
	        <dependency>
	          <groupId>org.mybatis.spring.boot</groupId>
	          <artifactId>mybatis-spring-boot-starter</artifactId>
	          <version>2.0.1</version>
	      </dependency>
	      <dependency>
	          <groupId>mysql</groupId>
	          <artifactId>mysql-connector-java</artifactId>
	          <scope>runtime</scope>
	      </dependency>
	        <dependency>
	          <groupId>org.springframework.boot</groupId>
	          <artifactId>spring-boot-starter-jdbc</artifactId>
	      </dependency>
	        <!-- for OAuth 2.0 -->
	        <dependency>
	            <groupId>org.springframework.security.oauth</groupId>
	            <artifactId>spring-security-oauth2</artifactId>
	        </dependency>
	        <dependency>
	            <groupId>org.projectlombok</groupId>
	            <artifactId>lombok</artifactId>
	        </dependency>
	    </dependencies>
</project>

        注意springboot的版本,如果太高或太低,可能会导致oauth的jar包找不到。

四、yml配置文件

        主要是配置数据源,和mybatis。因为通常情况下,为了完成测试直接把客户端信息,和用户信息存放在内存中,而本次实验将客户端信息和用户信息存放在了数据库中。所以需要为oauth提供数据源。

server: 
  port: 8080
  servlet:
    context-path: / #项目路径
 
spring: 
  datasource: 
    username: root
    password: jiajia123
    url: jdbc:mysql://localhost:3306/oauth?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
    driver-class-name: com.mysql.jdbc.Driver
 
mybatis: 
  mapper-locations: classpath:oauth2/mapper/*Mapper.xml
  type-aliases-package: oauth2.entity
  configuration:      
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #sql日志打印
        call-setters-on-nulls: true #解决返回类型为Map的时候如果值为null将不会封装此字段

五、数据库基本配置

        数据库中需要三张表,分别存放客户端信息,token等。sql脚本如下,我用的数据库是mysql5.7版本

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for oauth_access_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_access_token`;
CREATE TABLE `oauth_access_token`  (
  `token_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `token` blob NULL,
  `authentication_id` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `user_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authentication` blob NULL,
  `refresh_token` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`authentication_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `access_token_validity` int(11) NULL DEFAULT NULL,
  `refresh_token_validity` int(11) NULL DEFAULT NULL,
  `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for oauth_refresh_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `oauth_refresh_token`  (
  `token_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `token` blob NULL,
  `authentication` blob NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

六、授权服务器的主要配置

package oauth2.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import oauth2.service.UserService;
/**
 * 	授权服务器
 * @author 硅谷探秘者(jia)
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;
    /**
     * 	数据源
     */
    @Autowired
	private DataSource dataSource;
    
    @Autowired
    private UserService userService;
    
    /**
     * 	使用数据库的方式存储token,及每次产生的token都存放在数据库,每个用户只会产生一个
     * @return
     */
    @Bean
    public TokenStore tokenStore() {
        // return new InMemoryTokenStore(); //使用内存的方式储存token
        return new JdbcTokenStore(dataSource);
    }
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 用户认证
        endpoints.authenticationManager(authenticationManager);
        /**
         * 	从数据库中验证用户信息
         */
        endpoints.userDetailsService(userService);
        endpoints.tokenStore(tokenStore());
    }
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    	/**
    	 * 	从数据库中加载判断客户端信息
    	 */
    	clients.jdbc(dataSource);
    }
}

        可以看到代码中注入了数据源DataSource用于访问数据库。

        clients.jdbc(dataSource);这段代码配置从数据库获取判断客户端信息。

        endpoints.tokenStore(tokenStore());这是配置token的储存方式,InMemoryTokenStore()是将token储存在内存中,而JdbcTokenStore(dataSource)是将token储存在数据库中。

        endpoints.userDetailsService(userService);这行代码是配置用户的认证方式,这里配置的意思是我们要自定义从数据库中获取用户信息。userDetailsService方法需要一个UserDetailsService对象,所以我们要定义一个类实现UserDetailsService接口。如下:

package oauth2.service;
import org.springframework.security.core.userdetails.UserDetailsService;
public interface UserService extends UserDetailsService{
}
package oauth2.service.impl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import oauth2.entity.CustomUserDetails;
import oauth2.entity.User;
import oauth2.service.UserService;
@Service
public class UserServiceImpl implements UserService {
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    	/**
    	 * 	这里的s就是username,根据username去数据库查询密码
    	 */
    	System.out.println(s);
    	/**
    	 * 	模拟从数据库中获取用户信息
    	 */
        User user = new User();
        user.setUsername(s);
        user.setPassword("root");
        return new CustomUserDetails(user);
    }
}

        代码中在UserServiceImpl类中实现了UserDetailsService中的loadUserByUsername方法。他有一个参数s代表的就是第三方用户需要认证的用户名。此方法还需要返回一个UserDetails类型的对象,所以在此还需要定义两个类。

自定义用户

import java.io.Serializable;
public class User implements Serializable{
	private static final long serialVersionUID = 1L;
	private String username;
    private String password;
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}
import java.util.Collections;
public class CustomUserDetails extends org.springframework.security.core.userdetails.User {
	private static final long serialVersionUID = 1L;
	private User user;
    public CustomUserDetails(User user) {
     super(user.getUsername(), user.getPassword(), true, true, true, true, Collections.emptySet());
     this.user = user;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
}

        到此授权服务器和自定义用户认证方法的配置基本完成。

七、资源服务器安全配置

        主要配置需要鉴权的服务器资源。

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;
/***
 * 	资源服务器
 * @author 硅谷探秘者(jia)
 */
@Configuration
@EnableResourceServer
public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter {
	/**
	 * 	安全认证 配置需要鉴权的资源
	 */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .requestMatchers()
                .antMatchers("/**");
    }
}

        在此配置的是"/**"拦截所有的请求。

        再定义一个控制器用于测试

import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import oauth2.entity.CustomUserDetails;
import oauth2.entity.User;
/**
 * 测试controller
 * @author 硅谷探秘者(jia)
 */
@RestController
public class UserController {

    @GetMapping("/api/userInfo")
    public ResponseEntity<User> getUserInfo(){
    	CustomUserDetails user = (CustomUserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        String email = user.getUsername() + "@qq.com";
        User userInfo = new User();
        userInfo.setUsername(user.getUsername());
        return ResponseEntity.ok(userInfo);
    }
}

上述所有配置完成以后,就可以启动项目测试了。

下面提供一些客户端调用的测试代码

八、客户端测试需要资源

    <dependency>
		<groupId>commons-httpclient</groupId>
		<artifactId>commons-httpclient</artifactId>
		<version>3.1</version>
	</dependency>
	<dependency>
	    <groupId>com.alibaba</groupId>
	    <artifactId>fastjson</artifactId>
	    <version>1.2.47</version>
	</dependency>

九、主要测试代码

import java.io.IOException;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class TestMain {
	public static void main(String[] args) throws HttpException, IOException {
		/**
		 * 	获取token
		 */
		ResInfo res = getToken("http://localhost:8080/oauth/token?grant_type=password&scope=read&username=root&password=root");
		System.out.println(res.getAccess_token());
		
		/**
		 * 	获取用户信息
		 */
		System.out.println(getUserInfo("http://localhost:8080/api/userInfo",res.getAccess_token()));
	}
	
	public static ResInfo getToken(String urlParam) throws HttpException, IOException {
        // 创建httpClient实例对象
        HttpClient httpClient = new HttpClient();
        // 设置httpClient连接主机服务器超时时间:15000毫秒
        httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(15000);
        //username:password--->访问的用户名,密码,并使用base64进行加密,将加密的字节信息转化为string类型,encoding--->token
        String encoding = DatatypeConverter.printBase64Binary("app1:1234".getBytes("UTF-8"));
        // 创建GET请求方法实例对象
        PostMethod getMethod = new PostMethod(urlParam);
        // 设置post请求超时时间
        getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 60000);
        getMethod.setRequestHeader("Authorization", "Basic " + encoding);
        getMethod.addRequestHeader("Content-Type", "application/json");

        httpClient.executeMethod(getMethod);

        String result = getMethod.getResponseBodyAsString();
        getMethod.releaseConnection();
        ResInfo res=JSONObject.toJavaObject(JSON.parseObject(result),ResInfo.class);
        return res;
    }
	
	public static String getUserInfo(String urlParam,String token) throws HttpException, IOException {
        HttpClient httpClient = new HttpClient();
        httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(15000);
        GetMethod getMethod = new GetMethod(urlParam);
        getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 60000);
        getMethod.setRequestHeader("Authorization", "Bearer " + token);
        getMethod.addRequestHeader("Content-Type", "application/json");
        httpClient.executeMethod(getMethod);
        String result = getMethod.getResponseBodyAsString();
        getMethod.releaseConnection();
        return result;
	}
}

        正常情况下getToken方法返回

{"access_token":"25afcb6c-64b9-4a22-b83b-40876170c6d0","token_type":"bearer","expires_in":38828,"scope":"read"}

代表成功返回了token,此时可以拿着token请求 /api/userInfo 接口了,正常情况下返回

{"username":"root","password":null}

        在执行getToken方法的时候要特别注意请求头上的Authorization参数,它是  "Basic "+client_id+client_secret后经过base64加密后的字符串。在执行getUserInfo方法的时候也需要在请求头上携带Authorization参数,这个参数的形式是"Bearer "+token(token是在执行getToken方法是返回的)。

如果用户名或密码错误将会返回

 {"error":"invalid_grant","error_description":"坏的凭证"}

如果返回

{"timestamp":1577154713720,"status":401,"error":"Unauthorized","message":"Invalid basic authentication token","path":"/oauth/token"}

代表你的客户端配置信息有误。或者数据库中不存在该客户端的信息。注意客户端对的信息都保存再oauth_client_details表中,此时应该检查请求头上的Authorization参数是否正确,或检查数据库中是否有正确的客户端的配置信息。

        注意,在请求token的时候应采用post方式,如果使用get方式将会返回

{"error":"method_not_allowed","error_description":"Request method &#39;GET&#39; not supported"} 

错误信息。


评论区
请写下您的评论...
暂无评论...
猜你喜欢
java 1634 oauth2.0(demo)附sql脚本和测试代1.项目目录项目说明请搜索http://www.jiajiajia.club/search?str=oauth2.0
redis 729 配置文件中增加一项配置如下:#哨兵中设定主库与当前库同步,保证从库能够提升为主库masterauth"123456"在/etc/redis/目录下创sentinel.conf哨兵配置文件
java基础 2260 Java集合数据结构是以某种形将数据组织在一起的集合,它不仅存储数据,还支持访问和处理数据的操作。Java提供了几个能有效地组织和操作数据的数据结构,这些数据结构通常称为Java集合。在平
weblog 1983 java正则表达只能输入6-20位数字或字母[a-zA-Z0-9]{6,20}$
weblog 1049 java攻城尸,熟练使用各种,并知道它们实现的原理。jvm虚拟机原理、调优,懂得jvm能让你写出性能更好的代;池技术,什么对象池,连接池,线程池...:;java反射技术,写必备的技术,但是
spring/springmvc 2648 spring+springmvc+mybatis+maven项目(2)在spring+springmvc+mybatis+maven项目(1)中我们了基本的maven环境,并且可以运行项
rabbitmq,mqtt协议 526 集群目的就是为了实现rabbitmq的高可用性,集群分为2种普通集群:主备构,只是实现主备方案,不至于主节点宕机导致整个服务无法使用镜像集群:同步结构,基于普通集群实现的队列同步普通集群
java框架 1378 springboot整合elasticsearch实现全文索引demo配置说明参考:http://www.jiajiajia.club/blog/artical/Ja4t7X/378
归档
2018-11  12 2018-12  33 2019-01  28 2019-02  28 2019-03  32 2019-04  27 2019-05  33 2019-06  6 2019-07  12 2019-08  12 2019-09  21 2019-10  8 2019-11  15 2019-12  25 2020-01  9 2020-02  5 2020-03  16 2020-04  4 2020-06  1 2020-07  7 2020-08  13 2020-09  9 2020-10  5 2020-12  3 2021-01  1 2021-02  5 2021-03  7 2021-04  4 2021-05  4 2021-06  1 2021-07  7 2021-08  2 2021-09  8 2021-10  9 2021-11  16 2021-12  14 2022-01  7 2022-05  1 2022-08  3 2022-09  2 2022-10  2 2022-12  5 2023-01  3 2023-02  1 2023-03  4 2023-04  2 2023-06  3 2023-07  4 2023-08  1 2023-10  1 2024-02  1 2024-03  1 2024-04  1
标签
算法基础 linux 前端 c++ 数据结构 框架 数据库 计算机基础 储备知识 java基础 ASM 其他 深入理解java虚拟机 nginx git 消息中间件 搜索 maven redis docker dubbo vue 导入导出 软件使用 idea插件 协议 无聊的知识 jenkins springboot mqtt协议 keepalived minio mysql ensp 网络基础 xxl-job rabbitmq haproxy srs 音视频 webrtc javascript
目录
没有一个冬天不可逾越,没有一个春天不会来临。最慢的步伐不是跬步,而是徘徊,最快的脚步不是冲刺,而是坚持。