下面例子由三个项目组成,分别是 tools, server, client。
其中 tools 是密码生成工具
<?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>
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
<?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>
server.port=8000 server.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/netkiller?useSSL=false spring.datasource.username=netkiller spring.datasource.password=123123 spring.datasource.max-idle=10 spring.datasource.max-wait=10000 spring.datasource.min-idle=5 spring.datasource.initial-size=5 spring.datasource.validation-query=SELECT 1 spring.datasource.test-on-borrow=false spring.datasource.test-while-idle=true spring.datasource.time-between-eviction-runs-millis=18800 spring.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
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 AuthorizationServerConfigurer 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"); } }
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(); } }
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; } }
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); }
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); } }
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); } }
在 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 请使用上面提供的工具生成。
启动 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/hello Hello world !
<dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> </dependency>
package cn.netkiller.config; import static java.util.Arrays.asList; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; import org.springframework.security.oauth2.client.OAuth2RestOperations; import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.token.AccessTokenRequest; import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client; @EnableOAuth2Client @Configuration public class OAuth2ClientConfiguration { @Value("${oauth.resource:http://localhost:8080}") private String baseUrl; @Value("${oauth.authorize:http://localhost:8080/oauth/authorize}") private String authorizeUrl; @Value("${oauth.token:http://localhost:8080/oauth/token}") private String tokenUrl; @Bean protected OAuth2ProtectedResourceDetails resource() { ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails(); resource.setAccessTokenUri(tokenUrl); resource.setClientId("api"); resource.setClientSecret("secret"); resource.setGrantType("password"); resource.setScope(asList("read", "write")); resource.setUsername("netkiller@msn.com"); resource.setPassword("123456"); return resource; } @Bean public OAuth2RestOperations restTemplate() { AccessTokenRequest accessTokenRequest = new DefaultAccessTokenRequest(); return new OAuth2RestTemplate(resource(), new DefaultOAuth2ClientContext(accessTokenRequest)); } }
package cn.netkiller; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { System.out.println("Spring boot with Oauth2RestTemplate!"); SpringApplication.run(Application.class, args); } }
package cn.netkiller.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.client.OAuth2RestOperations; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class TestController { @Autowired private OAuth2RestOperations restTemplate; @GetMapping("/") @ResponseBody public String index() { OAuth2AccessToken token = restTemplate.getAccessToken(); System.out.println(token.getValue()); String tmp = restTemplate.getForObject("http://api.netkiller.cn/test.json", String.class); System.out.println(tmp); return tmp; } }
原文出处:Netkiller 系列 手札
本文作者:陈景峯
转载请与作者联系,同时请务必标明文章原始出处和作者信息及本声明。