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 条评论
登录 后参与评论

相关文章

来自专栏小狼的世界

惊闻NBC在奥运后放弃使用Silverlight

奥运初始的时候,媒体对于NBC使用Silverlight技术进行了高调的宣传,没想到奥运会结束刚刚三周的时间,NBC就弃用了Siverlight,重新采用Ado...

952
来自专栏菩提树下的杨过

Javascript:模仿淘宝的信用评价

老板昨天开会说:要给公司的购物平台增加信用评价功能,用户体验参考淘宝。 于是今天研究了一下,用jQuery模似一个类似的效果:  ? 代码如下: <!D...

2057
来自专栏区块链大本营

冻结黑客账户!6万EOS被盗的最新仲裁结果出来了

近期大热的EOS版狼人杀遭遇黑客攻击,60686.4190个EOS被盗,上线不到2天的EOS狼人游戏被迫停服。

482
来自专栏Java帮帮-微信公众号-技术文章全总结

【非技术面试】面试仪表你必须要注意

面试仪表你必须要注意 面试整体情况总结: 第一, 一定要展现出自己积极向上的精神面貌,服装要简洁、简单、大方。整洁就是整齐和干净,面试时穿着的服装必须是熨烫过或...

3224
来自专栏数据的力量

中国人骨子里的10大矛盾行为

我们身边或多或少会有这样的人:工作有爹安排,房子老爹给买,车子从爹那儿开,票子说来就来。他们撞人了,对受害者说“我爸是李刚”;他们考挂了,对老师说“我爸是XX”...

751
来自专栏CDA数据分析师

【戏说大数据】向林彪同志学习数据挖掘

【不打无把握之仗】 林彪从红军带兵时起,身上就有个小本子,上面记载着每次战斗的缴获、歼敌数量。每次打完仗,林彪就亲自往上面添加数字,并为之沾沾自喜…令人感觉...

1849
来自专栏程序员互动联盟

只有程序员能看懂的笑话

1. 某程序员退休后决定练习书法,于是重金购买文房四宝。一日,饭后突生雅兴,一番研墨拟纸,并点上上好檀香。定神片刻,泼墨挥毫,郑重地写下一行字:hello wo...

3305
来自专栏互联资讯

震撼穿越“亿年壶穴、天地奇观”!无人机成功穿越白云山亿年大壶穴!

福安白云山是史志记载的“闽东第一山”。因四季云雾缭绕,弥望如海,故名白云山。说起闽东宁德的风景,大家都不由想起太姥山和白水洋。可大家知道吗?福建福安白云山、屏南...

1134
来自专栏科技向令说

【新媒体】16个经典广告创意文案:拿走直接用!

某航天中心的指挥塔内,年轻人马克聚精会神地注视着面前的显示屏。忽然,显示屏上同时出现了两个移动的目标,而且这两个飞行物正越飞越近,有迎头相撞的危险。马克心急如焚...

974
来自专栏IT派

警方通报空姐遇害!滴滴100万悬赏嫌疑司机!请转发找凶手!

5月10日晚23:17,平安郑州发布关于空姐李明珠搭乘滴滴顺风车遇害案的警情通报!

1293

扫码关注云+社区