JWTの貼り付け
👏 ユーザー認証用にJWTを貼り付けます
JWTをユーザー認証ツールとして選びましたが、以前の使い方からすると、以前私が行っていた内容はほとんど必要ないので、最初から作業を再開する必要があります.満足です...
🔨 セキュリティ設定
先に設定した内容を優先して行います.
以前の設定
🔨 h 2設定の変更
h 2は、以前はh 2バッチを実行するために使用されていたが、springbootカーネルで実行できる.わかりましたが、こんなに早く忘れてしまいました.うううserver:
port: 8080
spring:
h2:
console:
enabled: true
datasource:
url: jdbc:h2:mem:logindb
driver-class-name: org.h2.Driver
username: sa
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
format_sql: true
show-sql: true
defer-datasource-initialization: true
logging:
level:
root: info
設定を上記に変更します.以前とは異なり、設定値はh 2:でenable:trueに内蔵されます.このように、サーバの実行時にh 2 dbが内蔵され、通常はテストに使用されます.
Spring 2.5以降では初期運転順序が変更され、この設定を外すとdefer-datasource-initialization: true
でエラーが発生するため、データが変更されます.sqlを書かないか、対応する設定を入れます!
👦 UserEntity
メンバーのデータベースの定義@Entity
@Table(name = "user")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserEntity {
@Id
@Column(name = "user_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
@Column(name = "username", length = 50, unique = true)
private String username;
@Column(name = "password", length = 100)
private String password;
@Column(name = "nickname", length = 50)
private String nickname;
@Column(name = "activated")
private boolean activated;
@ManyToMany
@JoinTable(
name = "user_authority",
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "authority_name")})
private Set<Authority> authorities;
}
UserEntityは次のように定義されています.@Entity
@Table(name = "authority")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Authority {
@Id
@Column(name = "authority_name", length = 50)
private String authorityName;
}
パーミッションのテーブルは、パーミッション名のみを持つように定義されます.また、上にjoin columnでuser id値が指定されているため、キー値はuser idとなる.INSERT INTO USER (USER_ID, USERNAME, PASSWORD, NICKNAME, ACTIVATED) VALUES (1, 'admin', '$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi', 'admin', 1);
INSERT INTO AUTHORITY (AUTHORITY_NAME) values ('ROLE_USER');
INSERT INTO AUTHORITY (AUTHORITY_NAME) values ('ROLE_ADMIN');
INSERT INTO USER_AUTHORITY (USER_ID, AUTHORITY_NAME) values (1, 'ROLE_USER');
INSERT INTO USER_AUTHORITY (USER_ID, AUTHORITY_NAME) values (1, 'ROLE_ADMIN');
そしてresourceフォルダの下にあるdata.サーバの実行時に情報を挿入するためにsqlファイルを作成します.@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//security 암호화 encoder를 bean으로 등록
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable() // rest api 이므로 기본설정 사용안함. 기본설정은 비인증시 로그인폼 화면으로 리다이렉트 된다.
.csrf().disable() // rest api이므로 csrf 보안이 필요없으므로 disable처리.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // jwt token으로 인증하므로 세션은 필요없으므로 생성안함.
.and()
.authorizeRequests() // 다음 리퀘스트에 대한 사용권한 체크
.antMatchers("/**").permitAll(); // 가입 및 인증 주소는 누구나 접근가능
//.antMatchers("/v1/**").permitAll() // 가입 및 인증 주소는 누구나 접근가능
//.anyRequest().hasRole("USER"); // 그외 나머지 요청은 모두 인증된 회원만 접근 가능
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers(
"/h2-console/**",
"/favicon.ico"
);
}
}
最後に、セキュリティをh 2コンソールとバビーコンソールにアクセスできるように設定します(=現在は必要ありません).
を選択します.
http://localhost:8080/h2-console
アクセスして接続し、正常に動作していることを確認できます.
🔨 JWT設定の追加 jwt:
header: Authorization
secret: juno-eats-toy-project-spring-boot-jwt-secret-login-api-juno-eats-toy-project-spring-boot-jwt-secret-login-api
application.ymlにjwtの設定を追加します.server:
port: 8080
spring:
h2:
console:
enabled: true
datasource:
url: jdbc:h2:mem:logindb
driver-class-name: org.h2.Driver
username: sa
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
format_sql: true
show-sql: true
defer-datasource-initialization: true
jwt:
header: Authorization
#HS512 알고리즘을 사용할 것이기 때문에 512bit, 즉 64byte 이상의 secret key를 사용해야 한다.
#echo 'juno-eats-toy-project-spring-boot-jwt-secret-login-api-juno-eats-toy-project-spring-boot-jwt-secret-login-api-secret'|base64
secret: juno-eats-toy-project-spring-boot-jwt-secret-login-api-juno-eats-toy-project-spring-boot-jwt-secret-login-api-secret
token-validity-in-seconds: 8460000
logging:
level:
root: info
全体の内容はこうです.
jwtライブラリをgradleに追加します.plugins {
id 'org.springframework.boot' version '2.6.2'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id "org.asciidoctor.jvm.convert" version "3.3.2" //(1) asciidoctor 추가
}
group = 'com.juno'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation' //validated 추가
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' //(2) mockMvc에서 restdocs를 사용할 수 있도록 추가
//jwt 추가
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'
}
ext {
snippetsDir = file('build/generated-snippets') //(3) 빌드시 snippets 파일들이 저장될 저장소
}
test {
useJUnitPlatform()
outputs.dir snippetsDir //(4) test 실행 시 파일을 (3)에서 설정한 저장소에 출력하도록 설정
}
asciidoctor { //(5) asccidoctor 설정
dependsOn test
inputs.dir snippetsDir
}
asciidoctor.doFirst { //(6) asciidoctor가 실행될 때 docs 하위 파일 삭제
delete file('src/main/resources/static/docs')
}
bootJar { //(7) bootJar 시 asciidoctor 종속되고 build하위 스니펫츠 파일을 classes 하위로 복사
dependsOn asciidoctor
copy {
from "${asciidoctor.outputDir}"
into 'BOOT-INF/classes/static/docs'
}
}
task copyDocument(type: Copy) { //(8) from의 파일을 into로 복사
dependsOn asciidoctor
from file("build/docs/asciidoc")
into file("src/main/resources/static/docs")
}
build { //(9) build 시 copyDocument 실행
dependsOn copyDocument
}
既存のプロファイルにはjwt構成のみが追加されています.
🔨 Token Providerの作成
Token ProviderはJWTでトークンの作成と有効性の検証を担当します.@Component
@Slf4j
public class TokenProvider implements InitializingBean {
private final String secretKey; //@Value를 통해 yml에 저장된 secret 값을 가져옴, Lombok valid 아님!
private final Long tokenValidityInMilliseconds; //토큰이 살아있는 시간 (1000L * 60 * 60 = 1시간)
private Key key; //afterPropertiesSet()에서 key값을 할당해줌
private final String AUTH = "auth";
//생성자에서 secretKey값을 base64로 인코딩하여 값을 넣어주고 토큰의 생명주기도 설정한다.
public TokenProvider(@Value("${jwt.secret}") String secretKey, @Value("${jwt.token-validity-in-seconds}") Long tokenValidityInMilliseconds) {
this.secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
this.tokenValidityInMilliseconds = tokenValidityInMilliseconds * 1000;
}
//bean이 생성되고 의존성 주입이 완료된 다음 init()을 실행하여 base64로 인코딩된 키 값을 decoding하여 key 변수에 할당
@Override
public void afterPropertiesSet() throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(secretKey);
this.key = Keys.hmacShaKeyFor(keyBytes);
}
//Authentication 객체의 권한정보를 이용해서 토큰을 생성하는 createToken 메서드
public String createToken(Authentication authentication){
String authorities = authentication.getAuthorities().stream() //authentication 정보를 가져옴
.map(GrantedAuthority::getAuthority) //GrantedAuthority 객체로 변환 후 getAuthority()
.collect(Collectors.joining(",")); //배열을 ,로 연결하여서 하나의 String으로 만듦
Date now = new Date();
long nowTime = now.getTime(); //현재 시간
Date validity = new Date(nowTime + this.tokenValidityInMilliseconds); //현재 시간 + 설정한 토큰 주기를 더해줌
return Jwts.builder() //토큰 생성후 리턴
.setSubject(authentication.getName()) //토큰 제목
.setIssuedAt(now) //토큰 발행 일자
.claim(AUTH, authorities) //담고 싶은 데이터 (key, value) = payload에 들어가는 정보, 여기서는 권한들에 대한 정보를 넣어줌
.signWith(key, SignatureAlgorithm.HS512) //key값과 함께 우리가 사용할 암호화 알고리즘 선언
.setExpiration(validity) //토큰 주기 설정
.compact();
}
//token을 파라미터로 받아서 토큰의 정보를 읽어온 후 정보를 확인하여 권한을 반환하는 메서드
public Authentication getAuthentication(String token){
Claims claims = Jwts
.parserBuilder()
.setSigningKey(key) //서명한 key 값
.build()
.parseClaimsJws(token) //전달 받은 token
.getBody(); //parse하여 전달된 값
Collection<GrantedAuthority> authorities = Arrays.stream(claims.get(AUTH).toString().split(",")) //파싱하여 가져온 claims에서 auth key 값으로 권한들을 가져와서 배열로 만듦
.map(SimpleGrantedAuthority::new) //배열을 스트림으로 변환 후 SimpleGrantedAuthority 객체로 변환
.collect(Collectors.toList()); //List로 만듦
User principal = new User(claims.getSubject(), "", authorities); //token에 저장된 제목으로 User 객체 생성
return new UsernamePasswordAuthenticationToken(principal, token, authorities); //인터페이스 Authentication의 구현체를 통해 Authentication객체 반환
}
//token의 유효성 검사, 각 Exception에 따라 처리 해주면 됨!
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); //토큰을 파싱했을 때 정상이 아니라면 exception 발생
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.info("잘못된 JWT 서명입니다.");
} catch (ExpiredJwtException e) {
log.info("만료된 JWT 토큰입니다.");
} catch (UnsupportedJwtException e) {
log.info("지원되지 않는 JWT 토큰입니다.");
} catch (IllegalArgumentException e) {
log.info("JWT 토큰이 잘못되었습니다.");
}
return false;
}
}
できるだけ注釈で詳細を書こうと思ったが、理解できるかどうか分からなかった.理解していなければ、大量のコード量から見るのではなく、一つの方法から見ると、大したことはないかもしれません.
🔨 Jwt Customフィルタ @Slf4j
@RequiredArgsConstructor
public class JwtFilter extends GenericFilterBean {
public static final String AUTHORIZATION_HEADER = "Authorization";
private final TokenProvider provider;
//jwt 토큰의 인증 정보를 현재 실행중인 security context에 저장하는 역할을 수행
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String jwt = resolveToken(httpServletRequest); //request로부터 header에서 토큰 정보를 가져옴
String requestURI = httpServletRequest.getRequestURI();
if (StringUtils.hasText(jwt) && provider.validateToken(jwt)) { //TokenProvider에 작성한 토큰 유효성검사 실행
Authentication authentication = provider.getAuthentication(jwt); //토큰에서 Authentication 객체를 반환 받고
SecurityContextHolder.getContext().setAuthentication(authentication); //security context에 저장
log.debug("Security Context에 '{}' 인증 정보를 저장했습니다, uri: {}", authentication.getName(), requestURI);
} else {
log.debug("유효한 JWT 토큰이 없습니다, uri: {}", requestURI); //유효성 검사 실패
}
chain.doFilter(request, response);
}
//reuqest의 header에서 토큰의 정보를 꺼내오기 위한 메서드
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
jwtトークンの認証情報をセキュリティコンテキストに格納するJwtFilterファイルが作成されました.
🔨 Jwt Security Config
JWTSecurityConfigファイルを作成し、上に作成したファイルをSecurityに適用します.@RequiredArgsConstructor
public class JwtSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
//TokenProvider를 주입
private final TokenProvider tokenProvider;
@Override
public void configure(HttpSecurity http) {
JwtFilter customFilter = new JwtFilter(tokenProvider); //JwtFilter를 통해 Security로직에 등록
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
🔨 JwtAuthenticationEntryPoint
アクセスしようとすると401 Unautorzedエラーが返されますが、有効な証明書は提供されていません.@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED); // 유효한 자격증명을 제공하지 않고 접근하려 할때 401
}
}
🔨 JwtAccessDeniedHandler
必要なパーミッションが存在しない場合は、403 Forbiddenエラーを返します.@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN); //필요한 권한이 없이 접근하려 할때 403
}
}
🔨 SecurityConfigのリセット @EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //@PreAuthorize를 메서드 단위로 사용하기 위해 어노테이션 추가
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
public SecurityConfig( //생성자 주입
TokenProvider tokenProvider,
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
JwtAccessDeniedHandler jwtAccessDeniedHandler
) {
this.tokenProvider = tokenProvider;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
}
@Bean
public PasswordEncoder passwordEncoder() { //password endcoder
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(
"/h2-console/**"
,"/favicon.ico"
,"/error"
);
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// token을 사용하는 방식이기 때문에 csrf를 disable합니다.
.csrf().disable()
//security exception handler
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
// enable h2-console
.and()
.headers()
.frameOptions()
.sameOrigin()
// 세션을 사용하지 않기 때문에 STATELESS로 설정
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/v1/hello").permitAll()
.antMatchers("/v1/authenticate").permitAll()
.antMatchers("/v1/signup").permitAll()
//그 외 요청은 권한을 가져야함
.anyRequest().authenticated()
.and()
//JwtSecurityConfig 적용
.apply(new JwtSecurityConfig(tokenProvider));
}
}
その後、サーバーを実行し、正常に起動すると、まず設定が終了します.
次に、securityとjwtがどのように機能しているかを書いて、この文章に追加しましょう.
以下の文章では、実際に操作されたコインの発行と会員認証を実施します.
Reference
この問題について(JWTの貼り付け), 我々は、より多くの情報をここで見つけました
https://velog.io/@ililil9482/JWT-붙이기
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
先に設定した内容を優先して行います.
以前の設定
🔨 h 2設定の変更
h 2は、以前はh 2バッチを実行するために使用されていたが、springbootカーネルで実行できる.わかりましたが、こんなに早く忘れてしまいました.うううserver:
port: 8080
spring:
h2:
console:
enabled: true
datasource:
url: jdbc:h2:mem:logindb
driver-class-name: org.h2.Driver
username: sa
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
format_sql: true
show-sql: true
defer-datasource-initialization: true
logging:
level:
root: info
設定を上記に変更します.以前とは異なり、設定値はh 2:でenable:trueに内蔵されます.このように、サーバの実行時にh 2 dbが内蔵され、通常はテストに使用されます.
Spring 2.5以降では初期運転順序が変更され、この設定を外すとdefer-datasource-initialization: true
でエラーが発生するため、データが変更されます.sqlを書かないか、対応する設定を入れます!
👦 UserEntity
メンバーのデータベースの定義@Entity
@Table(name = "user")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserEntity {
@Id
@Column(name = "user_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
@Column(name = "username", length = 50, unique = true)
private String username;
@Column(name = "password", length = 100)
private String password;
@Column(name = "nickname", length = 50)
private String nickname;
@Column(name = "activated")
private boolean activated;
@ManyToMany
@JoinTable(
name = "user_authority",
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "authority_name")})
private Set<Authority> authorities;
}
UserEntityは次のように定義されています.@Entity
@Table(name = "authority")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Authority {
@Id
@Column(name = "authority_name", length = 50)
private String authorityName;
}
パーミッションのテーブルは、パーミッション名のみを持つように定義されます.また、上にjoin columnでuser id値が指定されているため、キー値はuser idとなる.INSERT INTO USER (USER_ID, USERNAME, PASSWORD, NICKNAME, ACTIVATED) VALUES (1, 'admin', '$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi', 'admin', 1);
INSERT INTO AUTHORITY (AUTHORITY_NAME) values ('ROLE_USER');
INSERT INTO AUTHORITY (AUTHORITY_NAME) values ('ROLE_ADMIN');
INSERT INTO USER_AUTHORITY (USER_ID, AUTHORITY_NAME) values (1, 'ROLE_USER');
INSERT INTO USER_AUTHORITY (USER_ID, AUTHORITY_NAME) values (1, 'ROLE_ADMIN');
そしてresourceフォルダの下にあるdata.サーバの実行時に情報を挿入するためにsqlファイルを作成します.@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//security 암호화 encoder를 bean으로 등록
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable() // rest api 이므로 기본설정 사용안함. 기본설정은 비인증시 로그인폼 화면으로 리다이렉트 된다.
.csrf().disable() // rest api이므로 csrf 보안이 필요없으므로 disable처리.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // jwt token으로 인증하므로 세션은 필요없으므로 생성안함.
.and()
.authorizeRequests() // 다음 리퀘스트에 대한 사용권한 체크
.antMatchers("/**").permitAll(); // 가입 및 인증 주소는 누구나 접근가능
//.antMatchers("/v1/**").permitAll() // 가입 및 인증 주소는 누구나 접근가능
//.anyRequest().hasRole("USER"); // 그외 나머지 요청은 모두 인증된 회원만 접근 가능
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers(
"/h2-console/**",
"/favicon.ico"
);
}
}
最後に、セキュリティをh 2コンソールとバビーコンソールにアクセスできるように設定します(=現在は必要ありません).
を選択します.
http://localhost:8080/h2-console
アクセスして接続し、正常に動作していることを確認できます.
🔨 JWT設定の追加 jwt:
header: Authorization
secret: juno-eats-toy-project-spring-boot-jwt-secret-login-api-juno-eats-toy-project-spring-boot-jwt-secret-login-api
application.ymlにjwtの設定を追加します.server:
port: 8080
spring:
h2:
console:
enabled: true
datasource:
url: jdbc:h2:mem:logindb
driver-class-name: org.h2.Driver
username: sa
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
format_sql: true
show-sql: true
defer-datasource-initialization: true
jwt:
header: Authorization
#HS512 알고리즘을 사용할 것이기 때문에 512bit, 즉 64byte 이상의 secret key를 사용해야 한다.
#echo 'juno-eats-toy-project-spring-boot-jwt-secret-login-api-juno-eats-toy-project-spring-boot-jwt-secret-login-api-secret'|base64
secret: juno-eats-toy-project-spring-boot-jwt-secret-login-api-juno-eats-toy-project-spring-boot-jwt-secret-login-api-secret
token-validity-in-seconds: 8460000
logging:
level:
root: info
全体の内容はこうです.
jwtライブラリをgradleに追加します.plugins {
id 'org.springframework.boot' version '2.6.2'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id "org.asciidoctor.jvm.convert" version "3.3.2" //(1) asciidoctor 추가
}
group = 'com.juno'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation' //validated 추가
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' //(2) mockMvc에서 restdocs를 사용할 수 있도록 추가
//jwt 추가
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'
}
ext {
snippetsDir = file('build/generated-snippets') //(3) 빌드시 snippets 파일들이 저장될 저장소
}
test {
useJUnitPlatform()
outputs.dir snippetsDir //(4) test 실행 시 파일을 (3)에서 설정한 저장소에 출력하도록 설정
}
asciidoctor { //(5) asccidoctor 설정
dependsOn test
inputs.dir snippetsDir
}
asciidoctor.doFirst { //(6) asciidoctor가 실행될 때 docs 하위 파일 삭제
delete file('src/main/resources/static/docs')
}
bootJar { //(7) bootJar 시 asciidoctor 종속되고 build하위 스니펫츠 파일을 classes 하위로 복사
dependsOn asciidoctor
copy {
from "${asciidoctor.outputDir}"
into 'BOOT-INF/classes/static/docs'
}
}
task copyDocument(type: Copy) { //(8) from의 파일을 into로 복사
dependsOn asciidoctor
from file("build/docs/asciidoc")
into file("src/main/resources/static/docs")
}
build { //(9) build 시 copyDocument 실행
dependsOn copyDocument
}
既存のプロファイルにはjwt構成のみが追加されています.
🔨 Token Providerの作成
Token ProviderはJWTでトークンの作成と有効性の検証を担当します.@Component
@Slf4j
public class TokenProvider implements InitializingBean {
private final String secretKey; //@Value를 통해 yml에 저장된 secret 값을 가져옴, Lombok valid 아님!
private final Long tokenValidityInMilliseconds; //토큰이 살아있는 시간 (1000L * 60 * 60 = 1시간)
private Key key; //afterPropertiesSet()에서 key값을 할당해줌
private final String AUTH = "auth";
//생성자에서 secretKey값을 base64로 인코딩하여 값을 넣어주고 토큰의 생명주기도 설정한다.
public TokenProvider(@Value("${jwt.secret}") String secretKey, @Value("${jwt.token-validity-in-seconds}") Long tokenValidityInMilliseconds) {
this.secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
this.tokenValidityInMilliseconds = tokenValidityInMilliseconds * 1000;
}
//bean이 생성되고 의존성 주입이 완료된 다음 init()을 실행하여 base64로 인코딩된 키 값을 decoding하여 key 변수에 할당
@Override
public void afterPropertiesSet() throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(secretKey);
this.key = Keys.hmacShaKeyFor(keyBytes);
}
//Authentication 객체의 권한정보를 이용해서 토큰을 생성하는 createToken 메서드
public String createToken(Authentication authentication){
String authorities = authentication.getAuthorities().stream() //authentication 정보를 가져옴
.map(GrantedAuthority::getAuthority) //GrantedAuthority 객체로 변환 후 getAuthority()
.collect(Collectors.joining(",")); //배열을 ,로 연결하여서 하나의 String으로 만듦
Date now = new Date();
long nowTime = now.getTime(); //현재 시간
Date validity = new Date(nowTime + this.tokenValidityInMilliseconds); //현재 시간 + 설정한 토큰 주기를 더해줌
return Jwts.builder() //토큰 생성후 리턴
.setSubject(authentication.getName()) //토큰 제목
.setIssuedAt(now) //토큰 발행 일자
.claim(AUTH, authorities) //담고 싶은 데이터 (key, value) = payload에 들어가는 정보, 여기서는 권한들에 대한 정보를 넣어줌
.signWith(key, SignatureAlgorithm.HS512) //key값과 함께 우리가 사용할 암호화 알고리즘 선언
.setExpiration(validity) //토큰 주기 설정
.compact();
}
//token을 파라미터로 받아서 토큰의 정보를 읽어온 후 정보를 확인하여 권한을 반환하는 메서드
public Authentication getAuthentication(String token){
Claims claims = Jwts
.parserBuilder()
.setSigningKey(key) //서명한 key 값
.build()
.parseClaimsJws(token) //전달 받은 token
.getBody(); //parse하여 전달된 값
Collection<GrantedAuthority> authorities = Arrays.stream(claims.get(AUTH).toString().split(",")) //파싱하여 가져온 claims에서 auth key 값으로 권한들을 가져와서 배열로 만듦
.map(SimpleGrantedAuthority::new) //배열을 스트림으로 변환 후 SimpleGrantedAuthority 객체로 변환
.collect(Collectors.toList()); //List로 만듦
User principal = new User(claims.getSubject(), "", authorities); //token에 저장된 제목으로 User 객체 생성
return new UsernamePasswordAuthenticationToken(principal, token, authorities); //인터페이스 Authentication의 구현체를 통해 Authentication객체 반환
}
//token의 유효성 검사, 각 Exception에 따라 처리 해주면 됨!
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); //토큰을 파싱했을 때 정상이 아니라면 exception 발생
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.info("잘못된 JWT 서명입니다.");
} catch (ExpiredJwtException e) {
log.info("만료된 JWT 토큰입니다.");
} catch (UnsupportedJwtException e) {
log.info("지원되지 않는 JWT 토큰입니다.");
} catch (IllegalArgumentException e) {
log.info("JWT 토큰이 잘못되었습니다.");
}
return false;
}
}
できるだけ注釈で詳細を書こうと思ったが、理解できるかどうか分からなかった.理解していなければ、大量のコード量から見るのではなく、一つの方法から見ると、大したことはないかもしれません.
🔨 Jwt Customフィルタ @Slf4j
@RequiredArgsConstructor
public class JwtFilter extends GenericFilterBean {
public static final String AUTHORIZATION_HEADER = "Authorization";
private final TokenProvider provider;
//jwt 토큰의 인증 정보를 현재 실행중인 security context에 저장하는 역할을 수행
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String jwt = resolveToken(httpServletRequest); //request로부터 header에서 토큰 정보를 가져옴
String requestURI = httpServletRequest.getRequestURI();
if (StringUtils.hasText(jwt) && provider.validateToken(jwt)) { //TokenProvider에 작성한 토큰 유효성검사 실행
Authentication authentication = provider.getAuthentication(jwt); //토큰에서 Authentication 객체를 반환 받고
SecurityContextHolder.getContext().setAuthentication(authentication); //security context에 저장
log.debug("Security Context에 '{}' 인증 정보를 저장했습니다, uri: {}", authentication.getName(), requestURI);
} else {
log.debug("유효한 JWT 토큰이 없습니다, uri: {}", requestURI); //유효성 검사 실패
}
chain.doFilter(request, response);
}
//reuqest의 header에서 토큰의 정보를 꺼내오기 위한 메서드
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
jwtトークンの認証情報をセキュリティコンテキストに格納するJwtFilterファイルが作成されました.
🔨 Jwt Security Config
JWTSecurityConfigファイルを作成し、上に作成したファイルをSecurityに適用します.@RequiredArgsConstructor
public class JwtSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
//TokenProvider를 주입
private final TokenProvider tokenProvider;
@Override
public void configure(HttpSecurity http) {
JwtFilter customFilter = new JwtFilter(tokenProvider); //JwtFilter를 통해 Security로직에 등록
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
🔨 JwtAuthenticationEntryPoint
アクセスしようとすると401 Unautorzedエラーが返されますが、有効な証明書は提供されていません.@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED); // 유효한 자격증명을 제공하지 않고 접근하려 할때 401
}
}
🔨 JwtAccessDeniedHandler
必要なパーミッションが存在しない場合は、403 Forbiddenエラーを返します.@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN); //필요한 권한이 없이 접근하려 할때 403
}
}
🔨 SecurityConfigのリセット @EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //@PreAuthorize를 메서드 단위로 사용하기 위해 어노테이션 추가
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
public SecurityConfig( //생성자 주입
TokenProvider tokenProvider,
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
JwtAccessDeniedHandler jwtAccessDeniedHandler
) {
this.tokenProvider = tokenProvider;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
}
@Bean
public PasswordEncoder passwordEncoder() { //password endcoder
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(
"/h2-console/**"
,"/favicon.ico"
,"/error"
);
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// token을 사용하는 방식이기 때문에 csrf를 disable합니다.
.csrf().disable()
//security exception handler
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
// enable h2-console
.and()
.headers()
.frameOptions()
.sameOrigin()
// 세션을 사용하지 않기 때문에 STATELESS로 설정
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/v1/hello").permitAll()
.antMatchers("/v1/authenticate").permitAll()
.antMatchers("/v1/signup").permitAll()
//그 외 요청은 권한을 가져야함
.anyRequest().authenticated()
.and()
//JwtSecurityConfig 적용
.apply(new JwtSecurityConfig(tokenProvider));
}
}
その後、サーバーを実行し、正常に起動すると、まず設定が終了します.
次に、securityとjwtがどのように機能しているかを書いて、この文章に追加しましょう.
以下の文章では、実際に操作されたコインの発行と会員認証を実施します.
Reference
この問題について(JWTの貼り付け), 我々は、より多くの情報をここで見つけました
https://velog.io/@ililil9482/JWT-붙이기
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
server:
port: 8080
spring:
h2:
console:
enabled: true
datasource:
url: jdbc:h2:mem:logindb
driver-class-name: org.h2.Driver
username: sa
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
format_sql: true
show-sql: true
defer-datasource-initialization: true
logging:
level:
root: info
メンバーのデータベースの定義
@Entity
@Table(name = "user")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserEntity {
@Id
@Column(name = "user_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
@Column(name = "username", length = 50, unique = true)
private String username;
@Column(name = "password", length = 100)
private String password;
@Column(name = "nickname", length = 50)
private String nickname;
@Column(name = "activated")
private boolean activated;
@ManyToMany
@JoinTable(
name = "user_authority",
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "authority_name")})
private Set<Authority> authorities;
}
UserEntityは次のように定義されています.@Entity
@Table(name = "authority")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Authority {
@Id
@Column(name = "authority_name", length = 50)
private String authorityName;
}
パーミッションのテーブルは、パーミッション名のみを持つように定義されます.また、上にjoin columnでuser id値が指定されているため、キー値はuser idとなる.INSERT INTO USER (USER_ID, USERNAME, PASSWORD, NICKNAME, ACTIVATED) VALUES (1, 'admin', '$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi', 'admin', 1);
INSERT INTO AUTHORITY (AUTHORITY_NAME) values ('ROLE_USER');
INSERT INTO AUTHORITY (AUTHORITY_NAME) values ('ROLE_ADMIN');
INSERT INTO USER_AUTHORITY (USER_ID, AUTHORITY_NAME) values (1, 'ROLE_USER');
INSERT INTO USER_AUTHORITY (USER_ID, AUTHORITY_NAME) values (1, 'ROLE_ADMIN');
そしてresourceフォルダの下にあるdata.サーバの実行時に情報を挿入するためにsqlファイルを作成します.@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//security 암호화 encoder를 bean으로 등록
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable() // rest api 이므로 기본설정 사용안함. 기본설정은 비인증시 로그인폼 화면으로 리다이렉트 된다.
.csrf().disable() // rest api이므로 csrf 보안이 필요없으므로 disable처리.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // jwt token으로 인증하므로 세션은 필요없으므로 생성안함.
.and()
.authorizeRequests() // 다음 리퀘스트에 대한 사용권한 체크
.antMatchers("/**").permitAll(); // 가입 및 인증 주소는 누구나 접근가능
//.antMatchers("/v1/**").permitAll() // 가입 및 인증 주소는 누구나 접근가능
//.anyRequest().hasRole("USER"); // 그외 나머지 요청은 모두 인증된 회원만 접근 가능
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers(
"/h2-console/**",
"/favicon.ico"
);
}
}
最後に、セキュリティをh 2コンソールとバビーコンソールにアクセスできるように設定します(=現在は必要ありません).を選択します.
http://localhost:8080/h2-console
アクセスして接続し、正常に動作していることを確認できます.
🔨 JWT設定の追加 jwt:
header: Authorization
secret: juno-eats-toy-project-spring-boot-jwt-secret-login-api-juno-eats-toy-project-spring-boot-jwt-secret-login-api
application.ymlにjwtの設定を追加します.server:
port: 8080
spring:
h2:
console:
enabled: true
datasource:
url: jdbc:h2:mem:logindb
driver-class-name: org.h2.Driver
username: sa
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
format_sql: true
show-sql: true
defer-datasource-initialization: true
jwt:
header: Authorization
#HS512 알고리즘을 사용할 것이기 때문에 512bit, 즉 64byte 이상의 secret key를 사용해야 한다.
#echo 'juno-eats-toy-project-spring-boot-jwt-secret-login-api-juno-eats-toy-project-spring-boot-jwt-secret-login-api-secret'|base64
secret: juno-eats-toy-project-spring-boot-jwt-secret-login-api-juno-eats-toy-project-spring-boot-jwt-secret-login-api-secret
token-validity-in-seconds: 8460000
logging:
level:
root: info
全体の内容はこうです.
jwtライブラリをgradleに追加します.plugins {
id 'org.springframework.boot' version '2.6.2'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id "org.asciidoctor.jvm.convert" version "3.3.2" //(1) asciidoctor 추가
}
group = 'com.juno'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation' //validated 추가
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' //(2) mockMvc에서 restdocs를 사용할 수 있도록 추가
//jwt 추가
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'
}
ext {
snippetsDir = file('build/generated-snippets') //(3) 빌드시 snippets 파일들이 저장될 저장소
}
test {
useJUnitPlatform()
outputs.dir snippetsDir //(4) test 실행 시 파일을 (3)에서 설정한 저장소에 출력하도록 설정
}
asciidoctor { //(5) asccidoctor 설정
dependsOn test
inputs.dir snippetsDir
}
asciidoctor.doFirst { //(6) asciidoctor가 실행될 때 docs 하위 파일 삭제
delete file('src/main/resources/static/docs')
}
bootJar { //(7) bootJar 시 asciidoctor 종속되고 build하위 스니펫츠 파일을 classes 하위로 복사
dependsOn asciidoctor
copy {
from "${asciidoctor.outputDir}"
into 'BOOT-INF/classes/static/docs'
}
}
task copyDocument(type: Copy) { //(8) from의 파일을 into로 복사
dependsOn asciidoctor
from file("build/docs/asciidoc")
into file("src/main/resources/static/docs")
}
build { //(9) build 시 copyDocument 실행
dependsOn copyDocument
}
既存のプロファイルにはjwt構成のみが追加されています.
🔨 Token Providerの作成
Token ProviderはJWTでトークンの作成と有効性の検証を担当します.@Component
@Slf4j
public class TokenProvider implements InitializingBean {
private final String secretKey; //@Value를 통해 yml에 저장된 secret 값을 가져옴, Lombok valid 아님!
private final Long tokenValidityInMilliseconds; //토큰이 살아있는 시간 (1000L * 60 * 60 = 1시간)
private Key key; //afterPropertiesSet()에서 key값을 할당해줌
private final String AUTH = "auth";
//생성자에서 secretKey값을 base64로 인코딩하여 값을 넣어주고 토큰의 생명주기도 설정한다.
public TokenProvider(@Value("${jwt.secret}") String secretKey, @Value("${jwt.token-validity-in-seconds}") Long tokenValidityInMilliseconds) {
this.secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
this.tokenValidityInMilliseconds = tokenValidityInMilliseconds * 1000;
}
//bean이 생성되고 의존성 주입이 완료된 다음 init()을 실행하여 base64로 인코딩된 키 값을 decoding하여 key 변수에 할당
@Override
public void afterPropertiesSet() throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(secretKey);
this.key = Keys.hmacShaKeyFor(keyBytes);
}
//Authentication 객체의 권한정보를 이용해서 토큰을 생성하는 createToken 메서드
public String createToken(Authentication authentication){
String authorities = authentication.getAuthorities().stream() //authentication 정보를 가져옴
.map(GrantedAuthority::getAuthority) //GrantedAuthority 객체로 변환 후 getAuthority()
.collect(Collectors.joining(",")); //배열을 ,로 연결하여서 하나의 String으로 만듦
Date now = new Date();
long nowTime = now.getTime(); //현재 시간
Date validity = new Date(nowTime + this.tokenValidityInMilliseconds); //현재 시간 + 설정한 토큰 주기를 더해줌
return Jwts.builder() //토큰 생성후 리턴
.setSubject(authentication.getName()) //토큰 제목
.setIssuedAt(now) //토큰 발행 일자
.claim(AUTH, authorities) //담고 싶은 데이터 (key, value) = payload에 들어가는 정보, 여기서는 권한들에 대한 정보를 넣어줌
.signWith(key, SignatureAlgorithm.HS512) //key값과 함께 우리가 사용할 암호화 알고리즘 선언
.setExpiration(validity) //토큰 주기 설정
.compact();
}
//token을 파라미터로 받아서 토큰의 정보를 읽어온 후 정보를 확인하여 권한을 반환하는 메서드
public Authentication getAuthentication(String token){
Claims claims = Jwts
.parserBuilder()
.setSigningKey(key) //서명한 key 값
.build()
.parseClaimsJws(token) //전달 받은 token
.getBody(); //parse하여 전달된 값
Collection<GrantedAuthority> authorities = Arrays.stream(claims.get(AUTH).toString().split(",")) //파싱하여 가져온 claims에서 auth key 값으로 권한들을 가져와서 배열로 만듦
.map(SimpleGrantedAuthority::new) //배열을 스트림으로 변환 후 SimpleGrantedAuthority 객체로 변환
.collect(Collectors.toList()); //List로 만듦
User principal = new User(claims.getSubject(), "", authorities); //token에 저장된 제목으로 User 객체 생성
return new UsernamePasswordAuthenticationToken(principal, token, authorities); //인터페이스 Authentication의 구현체를 통해 Authentication객체 반환
}
//token의 유효성 검사, 각 Exception에 따라 처리 해주면 됨!
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); //토큰을 파싱했을 때 정상이 아니라면 exception 발생
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.info("잘못된 JWT 서명입니다.");
} catch (ExpiredJwtException e) {
log.info("만료된 JWT 토큰입니다.");
} catch (UnsupportedJwtException e) {
log.info("지원되지 않는 JWT 토큰입니다.");
} catch (IllegalArgumentException e) {
log.info("JWT 토큰이 잘못되었습니다.");
}
return false;
}
}
できるだけ注釈で詳細を書こうと思ったが、理解できるかどうか分からなかった.理解していなければ、大量のコード量から見るのではなく、一つの方法から見ると、大したことはないかもしれません.
🔨 Jwt Customフィルタ @Slf4j
@RequiredArgsConstructor
public class JwtFilter extends GenericFilterBean {
public static final String AUTHORIZATION_HEADER = "Authorization";
private final TokenProvider provider;
//jwt 토큰의 인증 정보를 현재 실행중인 security context에 저장하는 역할을 수행
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String jwt = resolveToken(httpServletRequest); //request로부터 header에서 토큰 정보를 가져옴
String requestURI = httpServletRequest.getRequestURI();
if (StringUtils.hasText(jwt) && provider.validateToken(jwt)) { //TokenProvider에 작성한 토큰 유효성검사 실행
Authentication authentication = provider.getAuthentication(jwt); //토큰에서 Authentication 객체를 반환 받고
SecurityContextHolder.getContext().setAuthentication(authentication); //security context에 저장
log.debug("Security Context에 '{}' 인증 정보를 저장했습니다, uri: {}", authentication.getName(), requestURI);
} else {
log.debug("유효한 JWT 토큰이 없습니다, uri: {}", requestURI); //유효성 검사 실패
}
chain.doFilter(request, response);
}
//reuqest의 header에서 토큰의 정보를 꺼내오기 위한 메서드
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
jwtトークンの認証情報をセキュリティコンテキストに格納するJwtFilterファイルが作成されました.
🔨 Jwt Security Config
JWTSecurityConfigファイルを作成し、上に作成したファイルをSecurityに適用します.@RequiredArgsConstructor
public class JwtSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
//TokenProvider를 주입
private final TokenProvider tokenProvider;
@Override
public void configure(HttpSecurity http) {
JwtFilter customFilter = new JwtFilter(tokenProvider); //JwtFilter를 통해 Security로직에 등록
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
🔨 JwtAuthenticationEntryPoint
アクセスしようとすると401 Unautorzedエラーが返されますが、有効な証明書は提供されていません.@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED); // 유효한 자격증명을 제공하지 않고 접근하려 할때 401
}
}
🔨 JwtAccessDeniedHandler
必要なパーミッションが存在しない場合は、403 Forbiddenエラーを返します.@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN); //필요한 권한이 없이 접근하려 할때 403
}
}
🔨 SecurityConfigのリセット @EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //@PreAuthorize를 메서드 단위로 사용하기 위해 어노테이션 추가
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
public SecurityConfig( //생성자 주입
TokenProvider tokenProvider,
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
JwtAccessDeniedHandler jwtAccessDeniedHandler
) {
this.tokenProvider = tokenProvider;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
}
@Bean
public PasswordEncoder passwordEncoder() { //password endcoder
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(
"/h2-console/**"
,"/favicon.ico"
,"/error"
);
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// token을 사용하는 방식이기 때문에 csrf를 disable합니다.
.csrf().disable()
//security exception handler
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
// enable h2-console
.and()
.headers()
.frameOptions()
.sameOrigin()
// 세션을 사용하지 않기 때문에 STATELESS로 설정
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/v1/hello").permitAll()
.antMatchers("/v1/authenticate").permitAll()
.antMatchers("/v1/signup").permitAll()
//그 외 요청은 권한을 가져야함
.anyRequest().authenticated()
.and()
//JwtSecurityConfig 적용
.apply(new JwtSecurityConfig(tokenProvider));
}
}
その後、サーバーを実行し、正常に起動すると、まず設定が終了します.
次に、securityとjwtがどのように機能しているかを書いて、この文章に追加しましょう.
以下の文章では、実際に操作されたコインの発行と会員認証を実施します.
Reference
この問題について(JWTの貼り付け), 我々は、より多くの情報をここで見つけました
https://velog.io/@ililil9482/JWT-붙이기
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
jwt:
header: Authorization
secret: juno-eats-toy-project-spring-boot-jwt-secret-login-api-juno-eats-toy-project-spring-boot-jwt-secret-login-api
server:
port: 8080
spring:
h2:
console:
enabled: true
datasource:
url: jdbc:h2:mem:logindb
driver-class-name: org.h2.Driver
username: sa
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
format_sql: true
show-sql: true
defer-datasource-initialization: true
jwt:
header: Authorization
#HS512 알고리즘을 사용할 것이기 때문에 512bit, 즉 64byte 이상의 secret key를 사용해야 한다.
#echo 'juno-eats-toy-project-spring-boot-jwt-secret-login-api-juno-eats-toy-project-spring-boot-jwt-secret-login-api-secret'|base64
secret: juno-eats-toy-project-spring-boot-jwt-secret-login-api-juno-eats-toy-project-spring-boot-jwt-secret-login-api-secret
token-validity-in-seconds: 8460000
logging:
level:
root: info
plugins {
id 'org.springframework.boot' version '2.6.2'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id "org.asciidoctor.jvm.convert" version "3.3.2" //(1) asciidoctor 추가
}
group = 'com.juno'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation' //validated 추가
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' //(2) mockMvc에서 restdocs를 사용할 수 있도록 추가
//jwt 추가
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'
}
ext {
snippetsDir = file('build/generated-snippets') //(3) 빌드시 snippets 파일들이 저장될 저장소
}
test {
useJUnitPlatform()
outputs.dir snippetsDir //(4) test 실행 시 파일을 (3)에서 설정한 저장소에 출력하도록 설정
}
asciidoctor { //(5) asccidoctor 설정
dependsOn test
inputs.dir snippetsDir
}
asciidoctor.doFirst { //(6) asciidoctor가 실행될 때 docs 하위 파일 삭제
delete file('src/main/resources/static/docs')
}
bootJar { //(7) bootJar 시 asciidoctor 종속되고 build하위 스니펫츠 파일을 classes 하위로 복사
dependsOn asciidoctor
copy {
from "${asciidoctor.outputDir}"
into 'BOOT-INF/classes/static/docs'
}
}
task copyDocument(type: Copy) { //(8) from의 파일을 into로 복사
dependsOn asciidoctor
from file("build/docs/asciidoc")
into file("src/main/resources/static/docs")
}
build { //(9) build 시 copyDocument 실행
dependsOn copyDocument
}
Token ProviderはJWTでトークンの作成と有効性の検証を担当します.
@Component
@Slf4j
public class TokenProvider implements InitializingBean {
private final String secretKey; //@Value를 통해 yml에 저장된 secret 값을 가져옴, Lombok valid 아님!
private final Long tokenValidityInMilliseconds; //토큰이 살아있는 시간 (1000L * 60 * 60 = 1시간)
private Key key; //afterPropertiesSet()에서 key값을 할당해줌
private final String AUTH = "auth";
//생성자에서 secretKey값을 base64로 인코딩하여 값을 넣어주고 토큰의 생명주기도 설정한다.
public TokenProvider(@Value("${jwt.secret}") String secretKey, @Value("${jwt.token-validity-in-seconds}") Long tokenValidityInMilliseconds) {
this.secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
this.tokenValidityInMilliseconds = tokenValidityInMilliseconds * 1000;
}
//bean이 생성되고 의존성 주입이 완료된 다음 init()을 실행하여 base64로 인코딩된 키 값을 decoding하여 key 변수에 할당
@Override
public void afterPropertiesSet() throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(secretKey);
this.key = Keys.hmacShaKeyFor(keyBytes);
}
//Authentication 객체의 권한정보를 이용해서 토큰을 생성하는 createToken 메서드
public String createToken(Authentication authentication){
String authorities = authentication.getAuthorities().stream() //authentication 정보를 가져옴
.map(GrantedAuthority::getAuthority) //GrantedAuthority 객체로 변환 후 getAuthority()
.collect(Collectors.joining(",")); //배열을 ,로 연결하여서 하나의 String으로 만듦
Date now = new Date();
long nowTime = now.getTime(); //현재 시간
Date validity = new Date(nowTime + this.tokenValidityInMilliseconds); //현재 시간 + 설정한 토큰 주기를 더해줌
return Jwts.builder() //토큰 생성후 리턴
.setSubject(authentication.getName()) //토큰 제목
.setIssuedAt(now) //토큰 발행 일자
.claim(AUTH, authorities) //담고 싶은 데이터 (key, value) = payload에 들어가는 정보, 여기서는 권한들에 대한 정보를 넣어줌
.signWith(key, SignatureAlgorithm.HS512) //key값과 함께 우리가 사용할 암호화 알고리즘 선언
.setExpiration(validity) //토큰 주기 설정
.compact();
}
//token을 파라미터로 받아서 토큰의 정보를 읽어온 후 정보를 확인하여 권한을 반환하는 메서드
public Authentication getAuthentication(String token){
Claims claims = Jwts
.parserBuilder()
.setSigningKey(key) //서명한 key 값
.build()
.parseClaimsJws(token) //전달 받은 token
.getBody(); //parse하여 전달된 값
Collection<GrantedAuthority> authorities = Arrays.stream(claims.get(AUTH).toString().split(",")) //파싱하여 가져온 claims에서 auth key 값으로 권한들을 가져와서 배열로 만듦
.map(SimpleGrantedAuthority::new) //배열을 스트림으로 변환 후 SimpleGrantedAuthority 객체로 변환
.collect(Collectors.toList()); //List로 만듦
User principal = new User(claims.getSubject(), "", authorities); //token에 저장된 제목으로 User 객체 생성
return new UsernamePasswordAuthenticationToken(principal, token, authorities); //인터페이스 Authentication의 구현체를 통해 Authentication객체 반환
}
//token의 유효성 검사, 각 Exception에 따라 처리 해주면 됨!
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); //토큰을 파싱했을 때 정상이 아니라면 exception 발생
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.info("잘못된 JWT 서명입니다.");
} catch (ExpiredJwtException e) {
log.info("만료된 JWT 토큰입니다.");
} catch (UnsupportedJwtException e) {
log.info("지원되지 않는 JWT 토큰입니다.");
} catch (IllegalArgumentException e) {
log.info("JWT 토큰이 잘못되었습니다.");
}
return false;
}
}
できるだけ注釈で詳細を書こうと思ったが、理解できるかどうか分からなかった.理解していなければ、大量のコード量から見るのではなく、一つの方法から見ると、大したことはないかもしれません.🔨 Jwt Customフィルタ @Slf4j
@RequiredArgsConstructor
public class JwtFilter extends GenericFilterBean {
public static final String AUTHORIZATION_HEADER = "Authorization";
private final TokenProvider provider;
//jwt 토큰의 인증 정보를 현재 실행중인 security context에 저장하는 역할을 수행
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String jwt = resolveToken(httpServletRequest); //request로부터 header에서 토큰 정보를 가져옴
String requestURI = httpServletRequest.getRequestURI();
if (StringUtils.hasText(jwt) && provider.validateToken(jwt)) { //TokenProvider에 작성한 토큰 유효성검사 실행
Authentication authentication = provider.getAuthentication(jwt); //토큰에서 Authentication 객체를 반환 받고
SecurityContextHolder.getContext().setAuthentication(authentication); //security context에 저장
log.debug("Security Context에 '{}' 인증 정보를 저장했습니다, uri: {}", authentication.getName(), requestURI);
} else {
log.debug("유효한 JWT 토큰이 없습니다, uri: {}", requestURI); //유효성 검사 실패
}
chain.doFilter(request, response);
}
//reuqest의 header에서 토큰의 정보를 꺼내오기 위한 메서드
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
jwtトークンの認証情報をセキュリティコンテキストに格納するJwtFilterファイルが作成されました.
🔨 Jwt Security Config
JWTSecurityConfigファイルを作成し、上に作成したファイルをSecurityに適用します.@RequiredArgsConstructor
public class JwtSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
//TokenProvider를 주입
private final TokenProvider tokenProvider;
@Override
public void configure(HttpSecurity http) {
JwtFilter customFilter = new JwtFilter(tokenProvider); //JwtFilter를 통해 Security로직에 등록
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
🔨 JwtAuthenticationEntryPoint
アクセスしようとすると401 Unautorzedエラーが返されますが、有効な証明書は提供されていません.@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED); // 유효한 자격증명을 제공하지 않고 접근하려 할때 401
}
}
🔨 JwtAccessDeniedHandler
必要なパーミッションが存在しない場合は、403 Forbiddenエラーを返します.@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN); //필요한 권한이 없이 접근하려 할때 403
}
}
🔨 SecurityConfigのリセット @EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //@PreAuthorize를 메서드 단위로 사용하기 위해 어노테이션 추가
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
public SecurityConfig( //생성자 주입
TokenProvider tokenProvider,
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
JwtAccessDeniedHandler jwtAccessDeniedHandler
) {
this.tokenProvider = tokenProvider;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
}
@Bean
public PasswordEncoder passwordEncoder() { //password endcoder
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(
"/h2-console/**"
,"/favicon.ico"
,"/error"
);
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// token을 사용하는 방식이기 때문에 csrf를 disable합니다.
.csrf().disable()
//security exception handler
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
// enable h2-console
.and()
.headers()
.frameOptions()
.sameOrigin()
// 세션을 사용하지 않기 때문에 STATELESS로 설정
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/v1/hello").permitAll()
.antMatchers("/v1/authenticate").permitAll()
.antMatchers("/v1/signup").permitAll()
//그 외 요청은 권한을 가져야함
.anyRequest().authenticated()
.and()
//JwtSecurityConfig 적용
.apply(new JwtSecurityConfig(tokenProvider));
}
}
その後、サーバーを実行し、正常に起動すると、まず設定が終了します.
次に、securityとjwtがどのように機能しているかを書いて、この文章に追加しましょう.
以下の文章では、実際に操作されたコインの発行と会員認証を実施します.
Reference
この問題について(JWTの貼り付け), 我々は、より多くの情報をここで見つけました
https://velog.io/@ililil9482/JWT-붙이기
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
@Slf4j
@RequiredArgsConstructor
public class JwtFilter extends GenericFilterBean {
public static final String AUTHORIZATION_HEADER = "Authorization";
private final TokenProvider provider;
//jwt 토큰의 인증 정보를 현재 실행중인 security context에 저장하는 역할을 수행
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String jwt = resolveToken(httpServletRequest); //request로부터 header에서 토큰 정보를 가져옴
String requestURI = httpServletRequest.getRequestURI();
if (StringUtils.hasText(jwt) && provider.validateToken(jwt)) { //TokenProvider에 작성한 토큰 유효성검사 실행
Authentication authentication = provider.getAuthentication(jwt); //토큰에서 Authentication 객체를 반환 받고
SecurityContextHolder.getContext().setAuthentication(authentication); //security context에 저장
log.debug("Security Context에 '{}' 인증 정보를 저장했습니다, uri: {}", authentication.getName(), requestURI);
} else {
log.debug("유효한 JWT 토큰이 없습니다, uri: {}", requestURI); //유효성 검사 실패
}
chain.doFilter(request, response);
}
//reuqest의 header에서 토큰의 정보를 꺼내오기 위한 메서드
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
JWTSecurityConfigファイルを作成し、上に作成したファイルをSecurityに適用します.
@RequiredArgsConstructor
public class JwtSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
//TokenProvider를 주입
private final TokenProvider tokenProvider;
@Override
public void configure(HttpSecurity http) {
JwtFilter customFilter = new JwtFilter(tokenProvider); //JwtFilter를 통해 Security로직에 등록
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
🔨 JwtAuthenticationEntryPoint
アクセスしようとすると401 Unautorzedエラーが返されますが、有効な証明書は提供されていません.@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED); // 유효한 자격증명을 제공하지 않고 접근하려 할때 401
}
}
🔨 JwtAccessDeniedHandler
必要なパーミッションが存在しない場合は、403 Forbiddenエラーを返します.@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN); //필요한 권한이 없이 접근하려 할때 403
}
}
🔨 SecurityConfigのリセット @EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //@PreAuthorize를 메서드 단위로 사용하기 위해 어노테이션 추가
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
public SecurityConfig( //생성자 주입
TokenProvider tokenProvider,
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
JwtAccessDeniedHandler jwtAccessDeniedHandler
) {
this.tokenProvider = tokenProvider;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
}
@Bean
public PasswordEncoder passwordEncoder() { //password endcoder
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(
"/h2-console/**"
,"/favicon.ico"
,"/error"
);
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// token을 사용하는 방식이기 때문에 csrf를 disable합니다.
.csrf().disable()
//security exception handler
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
// enable h2-console
.and()
.headers()
.frameOptions()
.sameOrigin()
// 세션을 사용하지 않기 때문에 STATELESS로 설정
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/v1/hello").permitAll()
.antMatchers("/v1/authenticate").permitAll()
.antMatchers("/v1/signup").permitAll()
//그 외 요청은 권한을 가져야함
.anyRequest().authenticated()
.and()
//JwtSecurityConfig 적용
.apply(new JwtSecurityConfig(tokenProvider));
}
}
その後、サーバーを実行し、正常に起動すると、まず設定が終了します.
次に、securityとjwtがどのように機能しているかを書いて、この文章に追加しましょう.
以下の文章では、実際に操作されたコインの発行と会員認証を実施します.
Reference
この問題について(JWTの貼り付け), 我々は、より多くの情報をここで見つけました
https://velog.io/@ililil9482/JWT-붙이기
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED); // 유효한 자격증명을 제공하지 않고 접근하려 할때 401
}
}
必要なパーミッションが存在しない場合は、403 Forbiddenエラーを返します.
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN); //필요한 권한이 없이 접근하려 할때 403
}
}
🔨 SecurityConfigのリセット @EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //@PreAuthorize를 메서드 단위로 사용하기 위해 어노테이션 추가
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
public SecurityConfig( //생성자 주입
TokenProvider tokenProvider,
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
JwtAccessDeniedHandler jwtAccessDeniedHandler
) {
this.tokenProvider = tokenProvider;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
}
@Bean
public PasswordEncoder passwordEncoder() { //password endcoder
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(
"/h2-console/**"
,"/favicon.ico"
,"/error"
);
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// token을 사용하는 방식이기 때문에 csrf를 disable합니다.
.csrf().disable()
//security exception handler
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
// enable h2-console
.and()
.headers()
.frameOptions()
.sameOrigin()
// 세션을 사용하지 않기 때문에 STATELESS로 설정
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/v1/hello").permitAll()
.antMatchers("/v1/authenticate").permitAll()
.antMatchers("/v1/signup").permitAll()
//그 외 요청은 권한을 가져야함
.anyRequest().authenticated()
.and()
//JwtSecurityConfig 적용
.apply(new JwtSecurityConfig(tokenProvider));
}
}
その後、サーバーを実行し、正常に起動すると、まず設定が終了します.
次に、securityとjwtがどのように機能しているかを書いて、この文章に追加しましょう.
以下の文章では、実際に操作されたコインの発行と会員認証を実施します.
Reference
この問題について(JWTの貼り付け), 我々は、より多くの情報をここで見つけました
https://velog.io/@ililil9482/JWT-붙이기
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //@PreAuthorize를 메서드 단위로 사용하기 위해 어노테이션 추가
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
public SecurityConfig( //생성자 주입
TokenProvider tokenProvider,
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
JwtAccessDeniedHandler jwtAccessDeniedHandler
) {
this.tokenProvider = tokenProvider;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
}
@Bean
public PasswordEncoder passwordEncoder() { //password endcoder
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(
"/h2-console/**"
,"/favicon.ico"
,"/error"
);
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// token을 사용하는 방식이기 때문에 csrf를 disable합니다.
.csrf().disable()
//security exception handler
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
// enable h2-console
.and()
.headers()
.frameOptions()
.sameOrigin()
// 세션을 사용하지 않기 때문에 STATELESS로 설정
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/v1/hello").permitAll()
.antMatchers("/v1/authenticate").permitAll()
.antMatchers("/v1/signup").permitAll()
//그 외 요청은 권한을 가져야함
.anyRequest().authenticated()
.and()
//JwtSecurityConfig 적용
.apply(new JwtSecurityConfig(tokenProvider));
}
}
Reference
この問題について(JWTの貼り付け), 我々は、より多くの情報をここで見つけました https://velog.io/@ililil9482/JWT-붙이기テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol