@EnableAuthorizationServer 을 설정하면 Authorization을 발급하는 서버로 지정된다.
이전 포스팅 참조.
이번엔 DB(Mysql)을 이용한 토큰발급을 설정한다.
AuthorizationServerConfigurerAdapter 를 구현하는 클래스를 만들고, @EnableAuthorizationServer를 추가한다.
참고로 dataSource 가 빈으로 생성되도록 미리 설정되어 있어야 한다.
application.properties 설정
spring.datasource.driver-class-name=[Driver]
spring.datasource.url=[접속URL]
spring.datasource.username=[계정]
spring.datasource.password=[비밀번호]
AuthorizationServerConfigurerAdapter을 구현한 클래스
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Value("${oauth2.client_id}") // client id. 프로퍼티에 등록
private String oauth2ClientId;
@Value("${oauth2.secret}") // secret. 프로퍼티에 등록
private String oauth2Secret;
private AuthenticationManager authenticationManager;
private DataSource dataSource;
public AuthorizationServerConfig(AuthenticationConfiguration authenticationConfiguration, DataSource dataSource) throws Exception {
this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
this.dataSource = dataSource;
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
endpoints.tokenStore(jdbcTokenStore());
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(oauth2ClientId)
.secret("{noop}" + oauth2Secret)
.autoApprove(true)
.redirectUris("/oauth2/callback")
.scopes("read", "write")
.authorizedGrantTypes("password", "refresh_token")
;
}
public TokenStore jdbcTokenStore() {
return new JdbcTokenStore(dataSource);
}
}
인증하기 위한 호출(GET)
http://localhost:8080/oauth/authorize?client_id=testClient&response_type=code
다음처럼 username, password 를 물어본다.
로그인을 클릭하면 AuthenticationProvider로 이동하는데, 여기서 로그인 유효성을 검토하게 설정하면 된다.
여기서는 간단하게 다음처럼 설정한다.
@Configuration
@RequiredArgsConstructor
public class OauthAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
System.out.println("username = " + username);
System.out.println("password = " + password);
checkLogin(username, password);
// 권한
Collection<? extends GrantedAuthority> authorities = getAuthorities();
return new UsernamePasswordAuthenticationToken(username, password, authorities);
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
/**
* 권한 추가
*/
private Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
grantedAuthorityList.add(new SimpleGrantedAuthority("ROLE_USER"));
return grantedAuthorityList;
}
private void checkLogin(String username, String password) {
if(username.equals("test") ) {
throw new BadCredentialsException("아이디 또는 패스워드가 틀립니다");
}
if(BCryptImpl.isMatch(password, "$2a$10$Ihw2gtO0/XSpdJftWFkjlePlCM6HoIhg44iwdc3vUUMn3EnJ3OgES") == false) {
throw new BadCredentialsException("아이디 또는 패스워드가 다릅니다");
}
}
}
여기서 로그인에 성공하면 oauth_access_token 테이블에 저장된다.(jdbc 로 연결 시)
하지만 이대로 하면 로그인 경로가 하나 더 있는 샘이라 보안이슈가 발생한다
때문에 oauth/authorize를 임의로 막을 필요가 있다
원본은 org.springframework.security.oauth2.provider.endpoint 패키지 내 AuthorizationEndpoint클래스에서 다음의 코드를 발견할 수 있다.
=> @RequestMapping(value = "/oauth/authorize")
다음과 같은 컨트롤러를 생성하여 여기를 바라보게 한다.
그리고 client_id와 secret을 파라미터로 받아서 비교한 뒤, 제대로 된 값을 호출했다면 Basic 토큰을 생성하여 리턴, 그러지 않으면 FAILED 를 리턴한다.
@RestController
public class AuthController {
@Value("${oauth2.client_id}")
private String oauth2ClientId;
@Value("${oauth2.secret}")
private String oauth2Secret;
// GET 으로 했던 인증을 POST로 변경하면서 커스텀
@PostMapping("/oauth/authorize")
public String oauthAuthorize(@RequestBody @Valid RequestOauthAuthorize request) {
if (request.getClientId().equals(oauth2ClientId) == false) {
return "FAILED";
}
if(request.getSecret().equals(oauth2Secret) == false) {
return "FAILED";
}
String credentials = request.getClientId()+":"+request.getSecret();
String encodedCredentials = new String(Base64.encode(credentials.getBytes()));
return "Basic " + encodedCredentials;
}
}
이제 이전처럼 oauth/authorize 를 호출하면 다음의 에러를 리턴한다.
그리고 다음과 같이 호출하면 Basic 토큰을 리턴받는다.
(사실 이 토큰은 clientId와 secret을 합쳐 만든 것이지 특별한 암호화가 적용된 것은 아니다)
이제 Basic [토큰] 을 이용해서 oauth/token을 호출해 accessToken을 받아보자.
Authorization 에는 방금 받은 토큰인 Basic dGVzdENsaWVudDp0ZXN0U2VjcmV0 를 Header에 추가한다.
그리고 유저로 로그인하기 위한 username과 password를 파라미터에 추가하여 호출한다.
grant_type 은 password 로 입력
호출해보면 다음과 같이 리턴된다.
{
"access_token": "62d85927-98e6-4623-96ff-8d38f9190d58",
"token_type": "bearer",
"refresh_token": "13f42190-00d0-491c-8d06-56d4a971eaea",
"expires_in": 43199,
"scope": "read write"
}
?) 왜 Basic [토큰] 이 필요할까?
accessToken을 받기 위해선 /oauth/token 를 호출해야 하는데 그러기 위해선 client_id와 secret으로 구성되어 있는 Basic 토큰이 필요하기 때문이다. 이것을 확인하기 위해 호출하는게 /oauth/authorize 이다.
끝.
'공부 > 프로그래밍' 카테고리의 다른 글
[aws] CodeDeploy(CI/CD) 자동 배포에 실패할때 복구방법 정리 (0) | 2020.11.18 |
---|---|
[springboot, slf4j] logging 파일분리 application.properties 에 설정하기(RollingFileAppender) (0) | 2020.11.16 |
[ajax, axios] Get호출 시 배열(list) 파라미터 전달하기 (1) | 2020.11.05 |
[aws] SSL (https) 인증서 발급 받기 & ELB(로드밸런서) 연결 (0) | 2020.10.23 |
[google map api] geocoder 사용 시 REQUEST_DENIED 나올때 (0) | 2020.10.14 |
댓글