前言
寫這個博客得原因是最近想要將自己得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:
成功。