前言
寫這個博客得原因是最近想要將自己得api單獨發布給第三方使用,但是又不想被別人濫用,所以想弄一個授權服務,但是網上關於oauth2.0的資料層出不窮,看了之后完全不明白應該如果實際的去整合,現在基本成功后記錄下來。
關於oauth2.0的概念以及相關的知識等可以建議參閱
理解OAuth 2.0。
准備
新建一個Spring Boot的web項目並導入一下依賴:
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
<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>
配置
配置EnableAuthorizationServer
@Configuration @EnableAuthorizationServer public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userService; @Autowired private TokenStore tokenStore; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("test")//客戶端ID .authorizedGrantTypes("password", "refresh_token")//設置驗證方式 .scopes("read", "write") .secret("123456") .accessTokenValiditySeconds(10000) //token過期時間 .refreshTokenValiditySeconds(10000); //refresh過期時間 } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore) .authenticationManager(authenticationManager) .userDetailsService(userService); //配置userService 這樣每次認證的時候會去檢驗用戶是否鎖定,有效等 } @Bean public TokenStore tokenStore() { //使用內存的tokenStore return new InMemoryTokenStore(); } }
UserService接口定義如下:
public interface UserService extends UserDetailsService { //后期在此新增UserService的業務接口 }
其中UserDetailsService只包含一個需要實現的方法,具體實現:
@Primary @Service("userService") public class UserServiceImpl implements UserService { private final static Set<User> users = new HashSet<>(); static { users.add(new User(1, "test-user1", "123451")); users.add(new User(2, "test-user2", "123452")); users.add(new User(3, "test-user3", "123453")); users.add(new User(4, "test-user4", "123454")); } @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { Optional<User> user = users.stream() .filter((u) -> u.getUserName().equals(s)) .findFirst(); if (!user.isPresent()) throw new UsernameNotFoundException("there's no user founded!"); else return UserDetailConverter.convert(user.get()); } private static class UserDetailConverter { static UserDetails convert(User user) { return new MyUserDetails(user); } } }
根據方法名很容易明白spring 的組件會調用此方法去獲取到用戶的信息並去驗證。
這里的用戶信息直接寫死的,實際中可以用jdbc或者其他配置等方式。
同時loadUserByUsername方法要求返回一個UserDetails接口:
public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled(); }
並且spring已經有了它的一個實現:org.springframework.security.core.userdetails.User類
由於User這個名字很容易和我們項目中的實體User重名這里選擇繼承這個類自己實現一個UserDetails:
/** * 自定義UserDetails類 攜帶User實例 */ public class MyUserDetails extends User { private com.example.oauth.pojo.User user; public MyUserDetails(com.example.oauth.pojo.User user) { super(user.getUserName(), user.getPassword(), true, true, true, true, Collections.EMPTY_SET); this.user = user; } public com.example.oauth.pojo.User getUser() { return user; } public void setUser(com.example.oauth.pojo.User user) { this.user = user; } }
到此基本的配置就已經完成了,
測試
寫一個測試的Controller:
@RestController public class UserController { @Autowired private TokenStore tokenStore; @PostMapping("/bar") public String bar(@RequestHeader("Authorization") String auth) { MyUserDetails userDetails = (MyUserDetails) tokenStore.readAuthentication(auth.split(" ")[1]).getPrincipal(); User user = userDetails.getUser(); return user.getUserName() + ":" + user.getPassword(); } }
啟動項目:
訪問:localhost:8080/bar
返回未授權
訪問:localhost:8080/oauth/token獲取token
這里的Authrization字段中Basic后面是配置中的clientId + ":" + secret的Base64編碼結果
我這里是:test:123456的Base64編碼結果
返回token信息:
使用token訪問controller:
成功。
