오픈 소스 인증 서버가 많이 있다고 생각해 더이상 지원하지 않기로 해서
AuthorizationServer를 만드는 것은 2.4.0 버전 이전의 버전들로 개발해야만 한다.
implementation 'org.springframework.security.oauth:spring-security-oauth2:2.3.8.RELEASE'
버전을 사용 예정이다.
최신 인증서버 구축은 이 곳을 참고하면 가능하다. 최신 버전의 경우 logout까지 프레임워크에서 이루어진다. 다만 인증방식의 일부가 deprecated되면서 deprecated된 인증방식 중 한가지방식이 필요하고 보안 이슈가 없다면 최신버전을 사용하지 않아도 된다.
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() // csrf 공격을 막기 위해 state 값을 전달 받지 않는다
.formLogin() // 기본 제공하는 로그인 화면 사용
.and()
.httpBasic(); // http 통신으로 basic auth를 사용 할 수 있다. (ex: Authorization: Basic bzFbdGfmZrptWY30YQ==)
}
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager(); // authenticationManager bean 생성 하여 셋팅 안할시 grant_type : password 지원 안함
}
@Bean
public UserDetailService userDetailService(){
return new UserDetailService();
}
}
@EnableAuthorizationServer
@Configuration
public class Oauth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private UserDetailService userDetailService;
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("permitAll()");
}
// client 설정
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory() // 클라이언트 정보는 메모리를 이용 한다.
.withClient("clientId") // 클라이언트 아이디
.secret("{noop}secretKey") // 시크릿키 ({} 안에 암호화 알고리즘을 명시 하면 된다. 암호화가 되어 있지 않다면 {noop}로 설정 해야 한다. 실제 요청은 암호화 방식인 {noop}를 입력 하지 않아도 된다.)
.authorizedGrantTypes("authorization_code", "password", "refresh_token", "client_credentials") // 가능한 토큰 발행 타입
.scopes("read", "write") // 가능한 접근 범위
.accessTokenValiditySeconds(60) // 토큰 유효 시간 : 1분
.refreshTokenValiditySeconds(60*60) // 토큰 유효 시간 : 1시간
.redirectUris("http://localhost:7070/callback") // 가능한 redirect uri
.autoApprove(true); // 권한 동의는 자동으로 yes (false 로 할시 권한 동의 여부를 묻는다.)
}
// 인증, 토큰 설정
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager) // grant_type password 를 사용하기 위함 (manager 지정 안할시 password type 으로 토큰 발행시 Unsupported grant type: password 오류 발생)
.userDetailsService(userDetailService); // refresh token 발행시 유저 정보 검사 하는데 사용하는 서비스 설정
}
}
@Entity
@Getter
@NoArgsConstructor
public class User implements UserDetails {
@Id
@Column(nullable = true, unique = true, length = 20)
private String id;
@Column(length = 100)
private String password;
@Column(nullable = false, unique = true, length = 100)
private String nickname;
@Column(nullable = true, unique = false)
private String state; // Y : 정상 회원 , L : 잠긴 계정, P : 패스워드 만료, A : 계정 만료
// security 기본 회원 정보인 UserDetails 클래스 implement 하기 위한 기본 함수들..
// 권한 (기본 권한 셋팅)
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.id;
}
// 계정 만료
@Override
public boolean isAccountNonExpired() {
if(StringUtils.equalsIgnoreCase(state, "A")){
return false;
}
return true;
}
// 잠긴 계정
@Override
public boolean isAccountNonLocked() {
if(StringUtils.equalsIgnoreCase(state, "L")){
return false;
}
return true;
}
// 패스워드 만료
@Override
public boolean isCredentialsNonExpired() {
if(StringUtils.equalsIgnoreCase(state, "P")){
return false;
}
return true;
}
@Override
public boolean isEnabled() {
if(isCredentialsNonExpired() && isAccountNonExpired() && isAccountNonLocked()){
return true;
}
return false;
}
}
public interface UserRepository extends JpaRepository<User, Long> {
User findById (String username);
}
@Service
public class UserDetailService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User userInfo = userRepository.findById(username);
return userInfo;
}
}