This commit is contained in:
kimscott
2019-09-02 11:19:16 +09:00
commit 0111ded181
14 changed files with 825 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
package com.example.template;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.example.template.entity.User;
import com.example.template.repository.UserRepository;
@SpringBootApplication
public class AuthorizationServerApplication implements CommandLineRunner {
public static void main( String[] args )
{
SpringApplication.run(AuthorizationServerApplication.class, args);
}
// TODO 삭제 - 테스트로 유저를 넣어서 확인하는 코드입니다.
// @Autowired
// private UserRepository repository;
// @Autowired private PasswordEncoder passwordEncoder;
@Override
public void run(String... args) throws Exception {
// User user = new User();
// user.setId(3l);
// user.setUsername("4@4.com");
// user.setPassword(passwordEncoder.encode("password"));
// user.setRole("USER_ADMIN");
//
// repository.save(user);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@@ -0,0 +1,188 @@
package com.example.template.config;
import com.example.template.entity.User;
import com.example.template.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
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;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import java.io.PrintWriter;
import java.security.KeyPair;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
public static final String CLIENT_ID = "uengine-client";
public static final String CLIENT_SECRET = "uengine-secret";
static final String GRANT_TYPE_PASSWORD = "password";
static final String AUTHORIZATION_CODE = "authorization_code";
static final String CLIENT_CREDENTIALS = "client_credentials";
static final String REFRESH_TOKEN = "refresh_token";
static final String IMPLICIT = "implicit";
static final String SCOPE_READ = "read";
static final String SCOPE_WRITE = "write";
static final String TRUST = "trust";
static final int ACCESS_TOKEN_VALIDITY_SECONDS = 24*60*60; // 24시간
static final int FREFRESH_TOKEN_VALIDITY_SECONDS = 6*60*60;
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
UserRepository userRepository;
@Autowired
private Environment env;
/**
* DB 설정을 별도로 하게 되면 에러가 발생한다.
* @throws Exception
*/
// @Bean
// public DataSource oauthDataSource() {
//
// DriverManagerDataSource dataSource = new DriverManagerDataSource();
// dataSource.setDriverClassName(env.getProperty("spring.datasource.driverClassName"));
// dataSource.setUrl(env.getProperty("spring.datasource.url"));
// dataSource.setUsername(env.getProperty("spring.datasource.username"));
// dataSource.setPassword(env.getProperty("spring.datasource.password"));
// return dataSource;
// }
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.accessDeniedHandler((request, response, exception)->{
response.setContentType("application/json;charset=UTF-8");
response.setHeader("Cache-Control", "no-cache");
PrintWriter writer = response.getWriter();
writer.println(new AccessDeniedException("access denied !"));
})
.authenticationEntryPoint((request, response, exception)->{
response.setContentType("application/json;charset=UTF-8");
response.setHeader("Cache-Control", "no-cache");
PrintWriter writer = response.getWriter();
writer.println(new AccessDeniedException("access denied !"));
})
;
}
/**
* 클라이언트 인증을 인메모리로 하였지만 클라이언트를 등록 및 별도로 관리하려면
* clientDetailsService 를 만든 후에
* clients.withClientDetails(clientDetailsService);
* 을 사용하면 된다.
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// clients.withClientDetails(clientDetailsService);
clients
.inMemory()
.withClient(CLIENT_ID)
.secret(passwordEncoder.encode(CLIENT_SECRET))
.authorizedGrantTypes(GRANT_TYPE_PASSWORD, CLIENT_CREDENTIALS, REFRESH_TOKEN, IMPLICIT )
.scopes(SCOPE_READ, SCOPE_WRITE, TRUST)
.authorities("ROLE_CLIENT","ROLE_TRUSTED_CLIENT")
.accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS).
refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(
Arrays.asList(tokenEnhancer(), accessTokenConverter()));
endpoints
.authenticationManager(authenticationManager)
.tokenStore(tokenStore()) //토큰과 관련된 인증 데이터를 저장, 검색, 제거, 읽기를 정의
.tokenEnhancer(tokenEnhancerChain)
;
}
@Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public KeyPair makeKeyPair(){
KeyPair keyPair = new KeyStoreKeyFactory(
new ClassPathResource("server.jks"), "qweqwe".toCharArray())
.getKeyPair("uengine", "qweqwe".toCharArray());
return keyPair;
}
/**
* JWT 토큰을 sign 하는 방법중 간단하게 쓰려면 Key 값을 쓰면되고,
* jks(java key store) 파일로 쓰려면 keypair 방식을 사용하면 됨
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(this.makeKeyPair()); // use Spring cloud gateway
// converter.setSigningKey("non-prod-signature");// use zuul 1
return converter;
}
/**
* JWT 토큰을 생성할때 추가적인 정보를 넣어주는 부분이다.
* 유저의 상세 정보를 추가하고 싶을때는 userService or userRepository 를 Autowired 하여 조회하여 데이터를 추가할수있다.
* @return
*/
@Bean
public TokenEnhancer tokenEnhancer(){
return (accessToken, authentication) -> {
if(authentication.isAuthenticated()) {
Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("company", "CLT");
// String clientId = authentication.getOAuth2Request().getClientId();
// logger.debug("client ID : " + clientId);
// ClientDetails client = clientService.loadClientByClientId(clientId);
// Map<String, Object> addInfo = client.getAdditionalInformation();
//// logger.debug("client : " + client.toString());
//
// if(addInfo!=null){
// for(String key:addInfo.keySet()){
// additionalInfo.put(key, addInfo.get(key));
// }
// }
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
}
return accessToken;
};
}
}

View File

@@ -0,0 +1,121 @@
package com.example.template.config;
import com.example.template.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import javax.annotation.PostConstruct;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private WebApplicationContext applicationContext;
private UserDetailsServiceImpl userDetailsService;
@Autowired
PasswordEncoder passwordEncoder;
private final AuthenticationManager authenticationManager;
@PostConstruct
public void completeSetup() {
userDetailsService = applicationContext.getBean(UserDetailsServiceImpl.class);
}
/**
* 생성자에 @Lazy 를 쓰는 이유는 Autowired 시 bean 이 등록된 후에 실행을 하기 위해서 사용되어진다.
* @param authenticationManager
*/
public WebSecurityConfig( @Lazy AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/css/**")
.antMatchers("/vendor/**")
.antMatchers("/js/**")
.antMatchers("/favicon*/**")
.antMatchers("/img/**")
;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* websecurity 부분에 핵심적인 endpoint 를 설정해 주는 부분이다.
* 특정 uri 를 허락하고싶다면
* .antMatchers("/login").permitAll() 등으로 사용이 가능하다.
* 현재는 cors 의 preflight 부분만 허용하고 있다.
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.authorizeRequests()
.antMatchers("/login").permitAll()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.anyRequest().authenticated()
.and()
.csrf()
.disable()
;
}
/**
* CORS 셋팅
* 만약 gateway 를 통해서 oauth 서버를 접근한다면 삭제해 줘야함
* @return
*/
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("*");
configuration.addAllowedMethod("*");
configuration.addAllowedHeader("*");
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}

View File

@@ -0,0 +1,57 @@
package com.example.template.entity;
import java.util.Collection;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@Entity
@Table(name = "user")
public class User implements UserDetails{
@Id
@Getter@Setter
private Long id;
@Getter@Setter
private String username;
@Column(length=400)
@Getter@Setter
private String password;
@Column
@Getter@Setter
private String role;
@Transient
@Getter@Setter
private Collection<? extends GrantedAuthority> authorities;
@Getter@Setter
private boolean accountNonExpired = true;
@Getter@Setter
private boolean accountNonLocked = true;
@Getter@Setter
private boolean credentialsNonExpired = true;
@Getter@Setter
private boolean enabled = true;
}

View File

@@ -0,0 +1,10 @@
package com.example.template.repository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.example.template.entity.User;
@Repository
public interface UserRepository extends CrudRepository<User, Long> {
User findByUsername(String username);
}

View File

@@ -0,0 +1,47 @@
package com.example.template.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
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 org.springframework.util.ObjectUtils;
import lombok.extern.slf4j.Slf4j;
import com.example.template.entity.User;
import com.example.template.repository.UserRepository;
import org.springframework.web.context.WebApplicationContext;
import javax.annotation.PostConstruct;
@Slf4j
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private WebApplicationContext applicationContext;
private UserRepository repository;
@PostConstruct
public void completeSetup() {
repository = applicationContext.getBean(UserRepository.class);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("UserDetailsServiceImpl.loadUserByUsername :::: {}",username);
User user = repository.findByUsername(username);
if(ObjectUtils.isEmpty(user)) {
throw new UsernameNotFoundException("Invalid resource owner, please check resource owner info !");
}
user.setAuthorities(AuthorityUtils.createAuthorityList(String.valueOf(user.getRole())));
return user;
}
}

View File

@@ -0,0 +1,23 @@
spring:
jpa:
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL57Dialect
show_sql: true
datasource:
url: jdbc:mysql://104.198.86.212:3306/uengine
username: root
password: test1234!@
# mybatis 설정시에는 driverClassName 을 넣어주어야함
driverClassName: com.mysql.cj.jdbc.Driver
# --- server
server:
port: 8090

Binary file not shown.