Spring boot with Oauth2

本文节选自《Netkiller Java 手札》

作者:netkiller 网站:http://www.netkiller.cn

5.20. Spring boot with Oauth2

下面例子由三个项目组成,分别是 tools, server, client。

其中 tools 是密码生成工具

5.20.1. Maven

<?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">
	<modelVersion>4.0.0</modelVersion>

	<groupId>cn.netkiller</groupId>
	<artifactId>oauth</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>pom</packaging>

	<name>oauth</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.6.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<dependencies>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- Security -->
		<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>
		</dependency>

	</dependencies>
	<modules>
		<module>server</module>
		<module>client</module>
	</modules>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build></project>

5.20.2. Password tools

Maven

<?xml version="1.0"?><project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>cn.netkiller</groupId>
    <artifactId>oauth</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>tools</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>tools</name>
  <url>http://maven.apache.org</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies></project>

下面的代码用于生成 Spring security 所用的密码

package cn.netkiller.oauth.tools;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;/**
 * Hello world!
 *
 */public class App {	public static void main(String[] args) {		int i = 0;		while (i < 10) {
			String password = "123456";
			BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
			String hashedPassword = passwordEncoder.encode(password);

			System.out.println(hashedPassword);
			i++;
		}
	}
}

运行后生成类似的密码

$2a$10$IrDG5Yr3CGorEg9gKG8smeRLnNUieCVyBCJHA80U6h20HDFCxd43W$2a$10$wseh5xFv1L3a3uHQId0MAOqAN0rKKcuX.RDaBPQ.pV/hsr80TqwxO$2a$10$xP3Gc/5/PN03BdkDfhUjAemTRVaiwr0lsaqPqD18UI.ho9nRC/ebW$2a$10$S.wLZ6e5YvmQA6mkX8yXWOdJbvahtDOesRu0ZwPOzAPCwpo7eDAsi$2a$10$Jo/yuWyiAZ2Lj8.ywoPl7OeOJYuP7RVq8l.qc/zOwtW8MTFp3NYGO$2a$10$eEvvjPok0fRK.DU6yF0qI.aucuiWr3y5G93SLq9/76ovcOwIuQAuS$2a$10$BWEkANxbgwATNQCEI9/uNevNEUNlomGY7cZ2CQVm.qCRcnyukT.Si$2a$10$69wSpyJQvjzJY7ou5PFWlOlEIecQukHV9WEq0nebsz5V6IZKfOVv2$2a$10$Cyj3hM39V34r5pMeQ.Y9peuUqYMBSvsJ7GTBgp4.stWaTtWMboYGS$2a$10$0/o4cRN2.tmnc58sH.N4WOsreVI6sWlPl4CCBrmUfJ332TMfRzA42

5.20.3. Server

5.20.3.1. Maven

<?xml version="1.0"?><project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>cn.netkiller</groupId>
		<artifactId>oauth</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<groupId>cn.netkiller</groupId>
	<artifactId>server</artifactId>
	<name>server</name>
	<description>Example Spring Boot REST project</description>

	<url>http://maven.apache.org</url>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>

	</dependencies></project>

5.20.3.2. application.properties

server.port=8000server.contextPath=/api

logging.level.com.gigy=DEBUG

# Data source properties
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/5kwords?useSSL=falsespring.datasource.username=5kwords
spring.datasource.password=5kwords
spring.datasource.max-idle=10spring.datasource.max-wait=10000spring.datasource.min-idle=5spring.datasource.initial-size=5spring.datasource.validation-query=SELECT 1spring.datasource.test-on-borrow=falsespring.datasource.test-while-idle=truespring.datasource.time-between-eviction-runs-millis=18800spring.datasource.jdbc-interceptors=ConnectionState;SlowQueryReport(threshold=0)

spring.jpa.database=MYSQL
spring.jpa.show-sql=true#spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.ddl-auto=create-drop
#spring.jpa.hibernate.ddl-auto=validate
#spring.jpa.show-sql=true

5.20.3.3. EnableAuthorizationServer

package cn.netkiller.oauth.server.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;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;

@Configuration@EnableAuthorizationServer
public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {

	@Autowired
	@Qualifier("userDetailsService")
	private UserDetailsService userDetailsService;

	@Autowired
	private AuthenticationManager authenticationManager;

	@Value("${oauth.tokenTimeout:3600}")	private int expiration;

	@Bean
	public PasswordEncoder passwordEncoder() {		return new BCryptPasswordEncoder();
	}

	@Override
	public void configure(AuthorizationServerEndpointsConfigurer configurer) throws Exception {		configurer.authenticationManager(authenticationManager);		configurer.userDetailsService(userDetailsService);
	}

	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {		clients.inMemory().withClient("api").secret("secret").accessTokenValiditySeconds(expiration).scopes("read", "write").authorizedGrantTypes("password", "refresh_token").resourceIds("resource");
	}

}

5.20.3.4. EnableResourceServer

package cn.netkiller.oauth.server.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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;@Configuration@EnableWebSecurity@EnableResourceServer@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {	/**
	 * Constructor disables the default security settings
	 */
	public WebSecurityConfiguration() {
		super(true);
	}	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/login");
	}	@Override
	public void configure(HttpSecurity http) throws Exception {
		http.antMatcher("/api/**").authorizeRequests().anyRequest().authenticated();
	}	@Bean
	@Override
	public AuthenticationManager authenticationManagerBean() throws Exception {
		return super.authenticationManagerBean();
	}
}

5.20.3.5. Entity Table

package cn.netkiller.oauth.server.model;import java.util.ArrayList;import java.util.Collection;import java.util.List;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.Table;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;@Entity@Table(name = "users")public class User implements UserDetails {	
	static final long serialVersionUID = 1L;	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)	@Column(name = "user_id", nullable = false, updatable = false)	private Long id;	
	@Column(name = "username", nullable = false, unique = true)	private String username;	
	@Column(name = "password", nullable = false)	private String password;	
	@Column(name = "enabled", nullable = false)	private boolean enabled;	public Collection<? extends GrantedAuthority> getAuthorities() {
		List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();		
		return authorities;
	}	public boolean isAccountNonExpired() {		return true;
	}	public boolean isAccountNonLocked() {		// we never lock accounts
		return true;
	}	public boolean isCredentialsNonExpired() {		// credentials never expire
		return true;
	}	public boolean isEnabled() {		return enabled;
	}	public String getPassword() {		return password;
	}	public String getUsername() {		return username;
	}

}

5.20.3.6. UserRepository

package cn.netkiller.oauth.server.repository;import org.springframework.data.jpa.repository.JpaRepository;import cn.netkiller.oauth.server.model.User;public interface UserRepository extends JpaRepository<User, Long> {	/**
	 * Find a user by username
	 *
	 * @param username
	 *            the user's username
	 * @return user which contains the user with the given username or null.
	 */
	User findOneByUsername(String username);

}

5.20.3.7. UserService

package cn.netkiller.oauth.server.service;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.Service;import cn.netkiller.oauth.server.repository.UserRepository;@Service("userDetailsService")public class UserService implements UserDetailsService {	@Autowired
	private UserRepository userRepository;	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {		return userRepository.findOneByUsername(username);
	}
}

5.20.3.8. TestRestController

package cn.netkiller.oauth.server.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/test")
public class TestRestController {	@RequestMapping(value = "hello", method = RequestMethod.GET)
	public ResponseEntity<String> hello() {
		return new ResponseEntity<String>("Hello world !", HttpStatus.OK);
	}	@PreAuthorize("#oauth2.hasScope('write')")	@RequestMapping(value = "set/{string}", method = RequestMethod.GET)
	public ResponseEntity<String> set(String string) {
		return new ResponseEntity<String>(string, HttpStatus.OK);
	}
}

5.20.3.9. 数据库初始化

在 src/main/resources 目录创建 data.sql 文件

INSERT INTO users (user_id, username, password, enabled) VALUES 
	('1', 'netkiller@msn.com', '$2a$10$Cyj3hM39V34r5pMeQ.Y9peuUqYMBSvsJ7GTBgp4.stWaTtWMboYGS', true);

此处密码 $2a$10$Cyj3hM39V34r5pMeQ.Y9peuUqYMBSvsJ7GTBgp4.stWaTtWMboYGS 请使用上面提供的工具生成。

5.20.3.10. Test

启动 Spring boot Server 项目

mvn spring-boot:run

启动后 Spring boot 会导入 data.sql 文件

mysql> select * from users where username="netkiller@msn.com";
+---------+---------+--------------------------------------------------------------+-------------------+| user_id | enabled | password                                                     | username          |
+---------+---------+--------------------------------------------------------------+-------------------+
|       4 |        | $2a$10$Cyj3hM39V34r5pMeQ.Y9peuUqYMBSvsJ7GTBgp4.stWaTtWMboYGS | netkiller@msn.com |+---------+---------+--------------------------------------------------------------+-------------------+1 row in set (0.00 sec)
MacBook-Pro:Application neo$ curl -X POST --user 'api:secret' -d 'grant_type=password&username=netkiller@msn.com&password=123456' http://localhost:8000/api/oauth/token{"access_token":"5bc0ee89-cd6d-47be-b31f-89c9e028159b","token_type":"bearer","refresh_token":"5107c09b-de85-4faf-8396-941572cf30d2","expires_in":3599,"scope":"read write"}MacBook-Pro:Application neo$ MacBook-Pro:Application neo$ curl -H "Accept: application/json" -H "Content-Type: application/json" -H "Authorization: Bearer 5bc0ee89-cd6d-47be-b31f-89c9e028159b" -X GET http://localhost:8000/api/test/helloHello world !

原文发布于微信公众号 - Netkiller(netkiller-ebook)

原文发表时间:2017-08-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Netkiller

Spring boot with Hive

本文节选自《Netkiller Java 手札》 摘要: spring boot 1.5.6 + hive 2.3.0 + hadoop 2.5.0 + hb...

6178
来自专栏码匠的流水账

聊聊EurekaRibbonClientConfiguration

spring-cloud-netflix-eureka-client-2.0.0.RELEASE-sources.jar!/org/springframewor...

301
来自专栏编程直播室

Spring Security判断用户是否已经登录方法一、JSP中检查user principal方法二、检查角色方法三、 还是查询用户方法四、 使用标签库方法五、 使用注解方法六、 编程

1345
来自专栏JadePeng的技术博客

Spring Security 架构与源码分析

Spring Security 主要实现了Authentication(认证,解决who are you? ) 和 Access Control(访问控制,也就...

740
来自专栏lgp20151222

Spring的注解@Qualifier小结

很明显了,在autoware时,由于有两个类实现了EmployeeService接口,所以Spring不知道应该绑定哪个实现类,所以抛出了如上错误。

331
来自专栏技术专栏

Spring boot+Spring security+JJWT 实现restful风格的权限验证

https://github.com/MarkGao11520/spring-boot-security-restful

741
来自专栏码匠的流水账

聊聊RibbonLoadBalancerClient的choose方法

本文主要研究一下RibbonLoadBalancerClient的choose方法

581
来自专栏码匠的流水账

spring security自定义指南

AuthenticationManager接口有个实现ProviderManager相当于一个provider chain,它里头有个List provider...

251
来自专栏一个会写诗的程序员的博客

页面获取Spring Security登录用户

spring security 把SPRING_SECURITY_CONTEXT 放入了session 没有直接把username 放进去。下面一段代码主要描述...

483
来自专栏码匠的流水账

spring security免登录动态配置方案2

之前有篇文章讲了怎么进行免登录动态配置的方案,动用了反射去实现,有点黑魔法的味道,这里再介绍另外一种方案

381

扫描关注云+社区