- SpringBoot2ë¡ Rest api ë§ë¤ê¸°(1) – Intellij Community íë¡ì í¸ìì±
- SpringBoot2ë¡ Rest api ë§ë¤ê¸°(2) â HelloWorld
- SpringBoot2ë¡ Rest api ë§ë¤ê¸°(3) â H2 Database ì°ë
- SpringBoot2ë¡ Rest api ë§ë¤ê¸°(4) â Swagger API 문ì ìëí
- SpringBoot2ë¡ Rest api ë§ë¤ê¸°(5) â API ì¸í°íì´ì¤ ë° ê²°ê³¼ ë°ì´í° 구조 ì¤ê³
- SpringBoot2ë¡ Rest api ë§ë¤ê¸°(6) â ControllerAdvice를 ì´ì©í Exceptionì²ë¦¬
- SpringBoot2ë¡ Rest api ë§ë¤ê¸°(7) â MessageSource를 ì´ì©í Exception ì²ë¦¬
- SpringBoot2ë¡ Rest api ë§ë¤ê¸°(8) â SpringSecurity 를 ì´ì©í ì¸ì¦ ë° ê¶íë¶ì¬
- SpringBoot2ë¡ Rest api ë§ë¤ê¸°(9) â Spring Starter Unit Test
- SpringBoot2ë¡ Rest api ë§ë¤ê¸°(10) â Social Login kakao
- SpringBoot2ë¡ Rest api ë§ë¤ê¸°(11) – profileì ì´ì©í íê²½ë³ ì¤ì ë¶ë¦¬
- SpringBoot2ë¡ Rest api ë§ë¤ê¸°(12) â Deploy & Nginx ì°ë & 무ì¤ë¨ ë°°í¬ í기
- SpringBoot2ë¡ Rest api ë§ë¤ê¸°(13) â Jenkins ë°°í¬(Deploy) + Git Tag Rollback
- SpringBoot2ë¡ Rest api ë§ë¤ê¸°(14) â ê°ë¨í JPA ê²ìí(board) ë§ë¤ê¸°
- SpringBoot2ë¡ Rest api ë§ë¤ê¸°(15) â Redisë¡ api ê²°ê³¼ ìºì±(Caching)ì²ë¦¬
- SpringBoot2ë¡ Rest api ë§ë¤ê¸°(16) â AOPì Custom Annotationì ì´ì©í ê¸ì¹ì´(Forbidden Word) ì²ë¦¬
ì´ë² ìê°ìë SpringSecurity를 ì´ì©íì¬ api ìë²ì ì¬ì© ê¶íì ì ííë ë°©ë²ì ëí´ ììë³´ëë¡ íê² ìµëë¤. ì§ê¸ê¹ì§ ê°ë°í apië ê¶í ë¶ì¬ 기ë¥ì´ ìì´ ë구ë íì ì 보를 ì¡°í, ìì± ë° ìì , ìì 를 í ì ìììµëë¤. ì´ ìíë¡ api를 ìë¹ì¤ì ë´ë³´ë¸ë¤ë©´ ë§ì 문ì ê° ë°ìí ê²ì ë¶ì ë³´ë¯ ë»í ì¼ì ëë¤. ì´ë¬í 문ì 를 í´ê²°í기 ìí´ ì¸ì¦í íìë§ api를 ì¬ì©í ì ìëë¡ ê°ì í´ ë³´ê² ìµëë¤.
SpringSecurity
ì¤íë§ììë ì¸ì¦ ë° ê¶í ë¶ì¬ë¥¼ íµí´ 리ìì¤ì ì¬ì©ì ì½ê² 컨í¸ë¡¤í ì ìë SpringSecurity framework를 ì ê³µíê³ ììµëë¤. Spring boot기ë°ì íë¡ì í¸ì SpringSecurity를 ì ì©íë©´ ë³´ì ê´ë ¨ ì²ë¦¬ë¥¼ ìì²´ì ì¼ë¡ 구íí íì ìì´ ì½ê² íìí 기ë¥ì 구íí ì ììµëë¤. ê°ëµíê² ìëì 그림ì²ë¼ SpringSecurityë Springì DispatcherServlet ìë¨ì Filter를 ë±ë¡ìì¼ ìì²ì ê°ë¡ì±ëë¤. í´ë¼ì´ì¸í¸ìê² ë¦¬ìì¤ ì ê·¼ ê¶íì´ ìì ê²½ì°ì ì¸ì¦(ex:ë¡ê·¸ì¸) íë©´ì¼ë¡ ìëì¼ë¡ 리ë¤ì´ë í¸ í©ëë¤.

SpringSecurity Filter
SpringSecurityë 기ë¥ë³ íí°ì ì§í©ì¼ë¡ ëì´ìê³ íí°ì ì²ë¦¬ ììë ìëì ê°ìµëë¤. ì¢
ë¥ê° ë§¤ì° ë§ì§ë§ ì¬ê¸°ì ì¤ìí ê²ì íí°ì ì²ë¦¬ ììì
ëë¤. í´ë¼ì´ì¸í¸ê° 리ìì¤ë¥¼ ìì²í ë ì ê·¼ ê¶íì´ ìë ê²½ì° ê¸°ë³¸ì ì¼ë¡ ë¡ê·¸ì¸ í¼ì¼ë¡ ë³´ë´ê² ëëë° ê·¸ ìí ì íë íí°ë UsernamePasswordAuthenticationFilterì
ëë¤.
Rest Apiììë ë¡ê·¸ì¸ í¼ì´ ë°ë¡ ìì¼ë¯ë¡ ì¸ì¦ ê¶íì´ ìë¤ë ì¤ë¥ Jsonì ë´ë ¤ì¤ì¼ íë¯ë¡
UsernamePasswordAuthenticationFilter ì ì ê´ë ¨ ì²ë¦¬ë¥¼ ë£ì´ì¼ í¨ì ì ì ììµëë¤.

- ChannelProcessingFilter
- SecurityContextPersistenceFilter
- ConcurrentSessionFilter
- HeaderWriterFilter
- CsrfFilter
- LogoutFilter
- X509AuthenticationFilter
- AbstractPreAuthenticatedProcessingFilter
- CasAuthenticationFilter
- UsernamePasswordAuthenticationFilter
- BasicAuthenticationFilter
- SecurityContextHolderAwareRequestFilter
- JaasApiIntegrationFilter
- RememberMeAuthenticationFilter
- AnonymousAuthenticationFilter
- SessionManagementFilter
- ExceptionTranslationFilter
- FilterSecurityInterceptor
- SwitchUserFilter
API ì¸ì¦ ë° ê¶í ë¶ì¬, ì íë 리ìì¤ì ìì²
- ì¸ì¦ì ìí´ ê°ì (Signup)ë° ë¡ê·¸ì¸(Signin) api를 구íí©ëë¤.
- ê°ì ì ì íë 리ìì¤ì ì ê·¼í ì ìë ROLE_USER ê¶íì íììê² ë¶ì¬í©ëë¤.
- SpringSecurity ì¤ì ìë ì ê·¼ ì íì´ íìí 리ìì¤ì ëí´ì ROLE_USER ê¶íì ê°ì ¸ì¼ ì ê·¼ ê°ë¥íëë¡ ì¸í í©ëë¤.
- ê¶íì ê°ì§ íìì´ ë¡ê·¸ì¸ ì±ê³µ ìì 리ìì¤ì ì ê·¼í ì ìë Jwt ë³´ì í í°ì ë°ê¸í©ëë¤.
- Jwt ë³´ì í í°ì¼ë¡ íìì ê¶íì´ íìí api 리ìì¤ë¥¼ ìì²íì¬ ì¬ì©í©ëë¤.
JWT ë?
JSON Web Token (JWT)ì JSON ê°ì²´ë¡ì ë¹ì¬ìê°ì ìì íê² ì 보를 ì ì¡í ì ìë ìê³ ë
립ì ì¸ ë°©ë²ì ì ìíë ê³µê° íì¤ (RFC 7519)ì
ëë¤. ìì¸í ë´ì©ì ìë ë§í¬ìì íì¸í ì ììµëë¤.
https://jwt.io/introduction/
Jwtë JSON ê°ì²´ë¥¼ ìí¸ííì¬ ë§ë Stringê°ì¼ë¡ 기본ì ì¼ë¡ ìí¸íëì´ìì´ ë³ì¡°íê¸°ê° ì´ë ¤ì´ ì ë³´ì
ëë¤. ëí ë¤ë¥¸ í í°ê³¼ ë¬ë¦¬ í í° ìì²´ì ë°ì´í°ë¥¼ ê°ì§ê³ ììµëë¤. api ìë²ììë ë¡ê·¸ì¸ì´ ìë£ë í´ë¼ì´ì¸í¸ìê² íìì 구ë¶í ì ìë ê°ì ë£ì Jwt í í°ì ìì±íì¬ ë°ê¸íê³ , í´ë¼ì´ì¸í¸ë ì´ Jwt í í°ì ì´ì©íì¬ ê¶íì´ íìí 리ìì¤ë¥¼ ìë²ì ìì²íë ë° ì¬ì©í ì ììµëë¤. apiìë²ë í´ë¼ì´ì¸í¸ìê²ì ì ë¬ë°ì Jwt í í°ì´ ì í¨íì§ íì¸íê³ ë´ê²¨ìë íì ì 보를 íì¸íì¬ ì íë 리ìì¤ë¥¼ ì ê³µíëë° ì´ì©í ì ììµëë¤.
build.gradleì library ì¶ê°
SpringSecurity ë° Jwtê´ë ¨ ë¼ì´ë¸ë¬ë¦¬ë¥¼ build.gradleì ì¶ê°í©ëë¤.
implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'io.jsonwebtoken:jjwt:0.9.1'
JwtTokenProvider ìì±
Jwt í í° ìì± ë° ì í¨ì± ê²ì¦ì íë ì»´í¬ëí¸ì ëë¤. Jwtë ì¬ë¬ ê°ì§ ìí¸í ìê³ ë¦¬ì¦ì ì ê³µíë©° ìê³ ë¦¬ì¦(SignatureAlgorithm.XXXXX)ê³¼ ë¹ë°í¤(secretKey)를 ê°ì§ê³ í í°ì ìì±íê² ë©ëë¤. ì´ë claimì ë³´ìë í í°ì ë¶ê°ì ì¼ë¡ ì¤ì´ ë³´ë¼ ì 보를 ì¸í í ì ììµëë¤. claim ì ë³´ì íìì 구ë¶í ì ìë ê°ì ì¸í íìë¤ê° í í°ì´ ë¤ì´ì¤ë©´ í´ë¹ ê°ì¼ë¡ íìì 구ë¶íì¬ ë¦¬ìì¤ë¥¼ ì ê³µíë©´ ë©ëë¤. ê·¸ë¦¬ê³ Jwtí í°ìë expire ìê°ì ì¸í í ì ììµëë¤. í í° ë°ê¸ í ì¼ì ìê° ì´íìë í í°ì ë§ë£ìí¤ë ë° ì¬ì©í ì ììµëë¤. resolveToken ë©ìëë Http request headerì ì¸í ë í í° ê°ì ê°ì ¸ì ì í¨ì±ì ì²´í¬í©ëë¤. ì íë 리ìì¤ë¥¼ ìì²í ë Http headerì í í°ì ì¸í íì¬ í¸ì¶íë©´ ì í¨ì±ì ê²ì¦íì¬ ì¬ì©ì ì¸ì¦ì í ì ììµëë¤.
package com.rest.api.config.security;
// import ìëµ
@RequiredArgsConstructor
@Component
public class JwtTokenProvider { // JWT í í°ì ìì± ë° ê²ì¦ 모ë
@Value("spring.jwt.secret")
private String secretKey;
private long tokenValidMilisecond = 1000L * 60 * 60; // 1ìê°ë§ í í° ì í¨
private final UserDetailsService userDetailsService;
@PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
// Jwt í í° ìì±
public String createToken(String userPk, List<String> roles) {
Claims claims = Jwts.claims().setSubject(userPk);
claims.put("roles", roles);
Date now = new Date();
return Jwts.builder()
.setClaims(claims) // ë°ì´í°
.setIssuedAt(now) // í í° ë°íì¼ì
.setExpiration(new Date(now.getTime() + tokenValidMilisecond)) // set Expire Time
.signWith(SignatureAlgorithm.HS256, secretKey) // ìí¸í ìê³ ë¦¬ì¦, secretê° ì¸í
.compact();
}
// Jwt í í°ì¼ë¡ ì¸ì¦ ì 보를 ì¡°í
public Authentication getAuthentication(String token) {
UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUserPk(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
// Jwt í í°ìì íì êµ¬ë³ ì ë³´ ì¶ì¶
public String getUserPk(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
// Requestì Headerìì token íì± : "X-AUTH-TOKEN: jwtí í°"
public String resolveToken(HttpServletRequest req) {
return req.getHeader("X-AUTH-TOKEN");
}
// Jwt í í°ì ì í¨ì± + ë§ë£ì¼ì íì¸
public boolean validateToken(String jwtToken) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtToken);
return !claims.getBody().getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}
}
spring:
datasource:
url: jdbc:h2:tcp://localhost/~/test
driver-class-name: org.h2.Driver
username: sa
jpa:
database-platform: org.hibernate.dialect.H2Dialect
properties.hibernate.hbm2ddl.auto: update
showSql: true
messages:
basename: i18n/exception
encoding: UTF-8
jwt:
secret: govlepel@$&
JwtAuthenticationFilter ìì±
Jwtê° ì í¨í í í°ì¸ì§ ì¸ì¦í기 ìí Filterì ëë¤. com.rest.api.config.security íìì Class를 ìì±í©ëë¤. ì´ íí°ë Security ì¤ì ì UsernamePasswordAuthenticationFilterìì ì¸í í ê²ì ëë¤.
package com.rest.api.config.security;
// import ìëµ
public class JwtAuthenticationFilter extends GenericFilterBean {
private JwtTokenProvider jwtTokenProvider;
// Jwt Provier 주ì
public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
// Requestë¡ ë¤ì´ì¤ë Jwt Tokenì ì í¨ì±ì ê²ì¦(jwtTokenProvider.validateToken)íë filter를 filterChainì ë±ë¡í©ëë¤.
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(request, response);
}
}
SpringSecurity Configuration
ìë²ì ë³´ì ì¤ì ì ì ì©í©ëë¤. com.rest.api.config.security íìì ë¤ì Class를 ìì±í©ëë¤. ì무ë ì ê·¼ ê°ë¥í 리ìì¤ë permitAll()ë¡ ì¸í
íê³ ëë¨¸ì§ ë¦¬ìì¤ë ë¤ìê³¼ ê°ì´ ‘ROLE_USER’ ê¶íì´ íìí¨ì¼ë¡ ëª
ìí©ëë¤. anyRequest().hasRole(“USER”) ëë anyRequest().authenticated()ë ëì¼í ëìì í©ëë¤.
ììì ì¤ëª
íë¯ì´ í´ë¹ filterë UsernamePasswordAuthenticationFilter ìì ì¤ì í´ì¼ í©ëë¤. SpringSecurity ì ì© íìë 모ë 리ìì¤ì ëí ì ê·¼ì´ ì íëë¯ë¡. Swagger íì´ì§ì ëí´ìë ìì¸ë¥¼ ì ì©í´ì¼ íì´ì§ì ì ê·¼í ì ììµëë¤. 리ìì¤ ì ê·¼ ì í ííìì ì¬ë¬ ê°ì§ê° ìì¼ë©° ë¤ìê³¼ ê°ìµëë¤.
hasIpAddress(ip) – ì ê·¼ìì IP주ìê° ë§¤ì¹ íëì§ íì¸í©ëë¤.
hasRole(role) – ìí ì´ ë¶ì¬ë ê¶í(Granted Authority)ê³¼ ì¼ì¹íëì§ íì¸í©ëë¤.
hasAnyRole(role) – ë¶ì¬ë ìí ì¤ ì¼ì¹íë íëª©ì´ ìëì§ íì¸í©ëë¤.
ex) access = “hasAnyRole(‘ROLE_USER’,’ROLE_ADMIN’)”
permitAll – 모ë ì ê·¼ì를 íì ì¹ì¸í©ëë¤.
denyAll – 모ë ì¬ì©ìì ì ê·¼ì ê±°ë¶í©ëë¤.
anonymous – ì¬ì©ìê° ìµëª
ì¬ì©ìì¸ì§ íì¸í©ëë¤.
authenticated – ì¸ì¦ë ì¬ì©ìì¸ì§ íì¸í©ëë¤.
rememberMe – ì¬ì©ìê° remember me를 ì¬ì©í´ ì¸ì¦íëì§ íì¸í©ëë¤.
fullyAuthenticated – ì¬ì©ìê° ëª¨ë í¬ë¦¬ë´ì
ì ê°ì¶ ìíìì ì¸ì¦íëì§ íì¸í©ëë¤.
package com.rest.api.config.security;
// import ìëµ
@RequiredArgsConstructor
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final JwtTokenProvider jwtTokenProvider;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@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("/*/signin", "/*/signup").permitAll() // ê°ì
ë° ì¸ì¦ 주ìë ë구ë ì ê·¼ê°ë¥
.antMatchers(HttpMethod.GET, "helloworld/**").permitAll() // hellowworldë¡ ììíë GETìì² ë¦¬ìì¤ë ë구ë ì ê·¼ê°ë¥
.anyRequest().hasRole("USER") // ê·¸ì¸ ëë¨¸ì§ ìì²ì 모ë ì¸ì¦ë íìë§ ì ê·¼ ê°ë¥
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); // jwt token íí°ë¥¼ id/password ì¸ì¦ íí° ì ì ë£ëë¤
}
@Override // ignore check swagger resource
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/v2/api-docs", "/swagger-resources/**",
"/swagger-ui.html", "/webjars/**", "/swagger/**");
}
}
Custom UserDetailsService ì ì
í í°ì ì¸í ë ì ì ì ë³´ë¡ íìì 보를 ì¡°ííë UserDetailsService를 ì¬ì ì í©ëë¤.
@RequiredArgsConstructor
@Service
public class CustomUserDetailService implements UserDetailsService {
private final UserJpaRepo userJpaRepo;
public UserDetails loadUserByUsername(String userPk) {
return userJpaRepo.findById(Long.valueOf(userPk)).orElseThrow(CUserNotFoundException::new);
}
}
User Entity ìì
SpringSecurityì ë³´ìì ì ì©í기 ìí´ User entityì UserDetails Class를 ììë°ì ì¶ê° ì 보를 ì¬ì ì í©ëë¤. rolesë íìì´ ê°ì§ê³ ìë ê¶í ì ë³´ì´ê³ , ê°ì
íì ëë 기본 “ROLE_USER”ê° ì¸í
ë©ëë¤. ê¶íì íìë¹ ì¬ë¬ ê°ê° ì¸í
ë ì ìì¼ë¯ë¡ Collectionì¼ë¡ ì ì¸í©ëë¤.
getUsernameì securityìì ì¬ì©íë íì êµ¬ë¶ idì
ëë¤. ì¬ê¸°ì uidë¡ ë³ê²½í©ëë¤.
ë¤ì ê°ë¤ì Securityìì ì¬ì©íë íì ìí ê°ì
ëë¤. ì¬ê¸°ì 모ë ì¬ì© ì íë¯ë¡ trueë¡ ì¤ì í©ëë¤.
Jsonê²°ê³¼ë¡ ì¶ë ¥ ì í ë°ì´í°ë @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) ì´ë
¸í
ì´ì
ì ì ì¸í´ì¤ëë¤.
isAccountNonExpired – ê³ì ì´ ë§ë£ê° ìëìëì§
isAccountNonLocked – ê³ì ì´ ì ê¸°ì§ ììëì§
isCredentialsNonExpired – ê³ì í¨ì¤ìëê° ë§ë£ ìë¬ëì§
isEnabled – ê³ì ì´ ì¬ì© ê°ë¥íì§
@Builder // builder를 ì¬ì©í ì ìê² í©ëë¤.
@Entity // jpa entityìì ì립ëë¤.
@Getter // user íëê°ì getter를 ìëì¼ë¡ ìì±í©ëë¤.
@NoArgsConstructor // ì¸ììë ìì±ì를 ìëì¼ë¡ ìì±í©ëë¤.
@AllArgsConstructor // ì¸ì를 모ë ê°ì¶ ìì±ì를 ìëì¼ë¡ ìì±í©ëë¤.
@Table(name = "user") // 'user' í
ì´ë¸ê³¼ 매íë¨ì ëª
ì
public class User implements UserDetails {
@Id // pk
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long msrl;
@Column(nullable = false, unique = true, length = 30)
private String uid;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Column(nullable = false, length = 100)
private String password;
@Column(nullable = false, length = 100)
private String name;
@ElementCollection(fetch = FetchType.EAGER)
@Builder.Default
private List<String> roles = new ArrayList<>();
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
}
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Override
public String getUsername() {
return this.uid;
}
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Override
public boolean isAccountNonExpired() {
return true;
}
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Override
public boolean isAccountNonLocked() {
return true;
}
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Override
public boolean isEnabled() {
return true;
}
}
UserJpaRepoì findByUidì¶ê°
íì ê°ì ì ê°ì í ì´ë©ì¼ ì¡°í를 ìí´ ë¤ì ë©ìë를 ì ì¸í©ëë¤.
public interface UserJpaRepo extends JpaRepository<User, Integer> {
Optional<User> findByUid(String email);
}
ë¡ê·¸ì¸ ìì¸ ì¶ê°
CEmailSigninFailedException ìì±
public class CEmailSigninFailedException extends RuntimeException {
public CEmailSigninFailedException(String msg, Throwable t) {
super(msg, t);
}
public CEmailSigninFailedException(String msg) {
super(msg);
}
public CEmailSigninFailedException() {
super();
}
}
# exception_en.yml emailSigninFailed: code: "-1001" msg: "Your account does not exist or your email or password is incorrect." # exception_ko.yml emailSigninFailed: code: "-1001" msg: "ê³ì ì´ ì¡´ì¬íì§ ìê±°ë ì´ë©ì¼ ëë ë¹ë°ë²í¸ê° ì ííì§ ììµëë¤."
# ExceptionAdvice
@ExceptionHandler(CEmailSigninFailedException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
protected CommonResult emailSigninFailed(HttpServletRequest request, CEmailSigninFailedException e) {
return responseService.getFailResult(Integer.valueOf(getMessage("emailSigninFailed.code")), getMessage("emailSigninFailed.msg"));
}
ê°ì / ë¡ê·¸ì¸ Controller ì¶ê°
ë¡ê·¸ì¸ ì±ê³µ ììë ê²°ê³¼ë¡ Jwtí í°ì ë°ê¸í©ëë¤.
ê°ì
ììë í¨ì¤ìë ì¸ì½ë©ì ìí´ passwordEncoderì¤ì ì í©ëë¤. 기본 ì¤ì ì bcrypt encodingì´ ì¬ì©ë©ëë¤.
@Api(tags = {"1. Sign"})
@RequiredArgsConstructor
@RestController
@RequestMapping(value = "/v1")
public class SignController {
private final UserJpaRepo userJpaRepo;
private final JwtTokenProvider jwtTokenProvider;
private final ResponseService responseService;
private final PasswordEncoder passwordEncoder;
@ApiOperation(value = "ë¡ê·¸ì¸", notes = "ì´ë©ì¼ íì ë¡ê·¸ì¸ì íë¤.")
@PostMapping(value = "/signin")
public SingleResult<String> signin(@ApiParam(value = "íìID : ì´ë©ì¼", required = true) @RequestParam String id,
@ApiParam(value = "ë¹ë°ë²í¸", required = true) @RequestParam String password) {
User user = userJpaRepo.findByUid(id).orElseThrow(CEmailSigninFailedException::new);
if (!passwordEncoder.matches(password, user.getPassword()))
throw new CEmailSigninFailedException();
return responseService.getSingleResult(jwtTokenProvider.createToken(String.valueOf(user.getMsrl()), user.getRoles()));
}
@ApiOperation(value = "ê°ì
", notes = "íìê°ì
ì íë¤.")
@PostMapping(value = "/signup")
public CommonResult signin(@ApiParam(value = "íìID : ì´ë©ì¼", required = true) @RequestParam String id,
@ApiParam(value = "ë¹ë°ë²í¸", required = true) @RequestParam String password,
@ApiParam(value = "ì´ë¦", required = true) @RequestParam String name) {
userJpaRepo.save(User.builder()
.uid(id)
.password(passwordEncoder.encode(password))
.name(name)
.roles(Collections.singletonList("ROLE_USER"))
.build());
return responseService.getSuccessResult();
}
}
SpringRestApiApplicationì passwordEncoder bean ì¶ê°
@SpringBootApplication
public class SpringRestApiApplication {
public static void main(String[] args) {
SpringApplication.run(SpringRestApiApplication.class, args);
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
UserController ìì
ì í¨í Jwtí í°ì ì¤ì í´ì¼ë§ User 리ìì¤ë¥¼ ì¬ì©í ì ìëë¡ Headerì X-AUTH-TOKENì ì¸ìë¡ ë°ëë¡ ì ì¸í©ëë¤.
@Api(tags = {"2. User"})
@RequiredArgsConstructor
@RestController
@RequestMapping(value = "/v1")
public class UserController {
private final UserJpaRepo userJpaRepo;
private final ResponseService responseService; // 결과를 ì²ë¦¬í Service
@ApiImplicitParams({
@ApiImplicitParam(name = "X-AUTH-TOKEN", value = "ë¡ê·¸ì¸ ì±ê³µ í access_token", required = true, dataType = "String", paramType = "header")
})
@ApiOperation(value = "íì 리ì¤í¸ ì¡°í", notes = "모ë íìì ì¡°ííë¤")
@GetMapping(value = "/users")
public ListResult<User> findAllUser() {
// ê²°ê³¼ë°ì´í°ê° ì¬ë¬ê±´ì¸ê²½ì° getListResult를 ì´ì©í´ì 결과를 ì¶ë ¥íë¤.
return responseService.getListResult(userJpaRepo.findAll());
}
@ApiImplicitParams({
@ApiImplicitParam(name = "X-AUTH-TOKEN", value = "ë¡ê·¸ì¸ ì±ê³µ í access_token", required = false, dataType = "String", paramType = "header")
})
@ApiOperation(value = "íì ë¨ê±´ ì¡°í", notes = "íìë²í¸(msrl)ë¡ íìì ì¡°ííë¤")
@GetMapping(value = "/user")
public SingleResult<User> findUserById(@ApiParam(value = "ì¸ì´", defaultValue = "ko") @RequestParam String lang) {
// SecurityContextìì ì¸ì¦ë°ì íìì ì 보를 ì»ì´ì¨ë¤.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String id = authentication.getName();
// ê²°ê³¼ë°ì´í°ê° ë¨ì¼ê±´ì¸ê²½ì° getSingleResult를 ì´ì©í´ì 결과를 ì¶ë ¥íë¤.
return responseService.getSingleResult(userJpaRepo.findByUid(id).orElseThrow(CUserNotFoundException::new));
}
@ApiImplicitParams({
@ApiImplicitParam(name = "X-AUTH-TOKEN", value = "ë¡ê·¸ì¸ ì±ê³µ í access_token", required = true, dataType = "String", paramType = "header")
})
@ApiOperation(value = "íì ìì ", notes = "íìì 보를 ìì íë¤")
@PutMapping(value = "/user")
public SingleResult<User> modify(
@ApiParam(value = "íìë²í¸", required = true) @RequestParam int msrl,
@ApiParam(value = "íìì´ë¦", required = true) @RequestParam String name) {
User user = User.builder()
.msrl(msrl)
.name(name)
.build();
return responseService.getSingleResult(userJpaRepo.save(user));
}
@ApiImplicitParams({
@ApiImplicitParam(name = "X-AUTH-TOKEN", value = "ë¡ê·¸ì¸ ì±ê³µ í access_token", required = true, dataType = "String", paramType = "header")
})
@ApiOperation(value = "íì ìì ", notes = "userIdë¡ íìì 보를 ìì íë¤")
@DeleteMapping(value = "/user/{msrl}")
public CommonResult delete(
@ApiParam(value = "íìë²í¸", required = true) @PathVariable int msrl) {
userJpaRepo.deleteById(msrl);
// ì±ê³µ ê²°ê³¼ ì ë³´ë§ íìíê²½ì° getSuccessResult()를 ì´ì©íì¬ ê²°ê³¼ë¥¼ ì¶ë ¥íë¤.
return responseService.getSuccessResult();
}
}
Test Swagger
íìê°ì -> ë¡ê·¸ì¸ -> í í°ì ì´ì©í íìì ë³´ ì¡°íìì¼ë¡ Test를 ì§íí©ëë¤.



ì¸ì¦ í í°ì ë°ê¸ë°ì ì íë 리ìì¤ì ì ê·¼íë ê²ì ëí í ì¤í¸ê° ìë£ëììµëë¤.
ìì¸ ì²ë¦¬ ë³´ì
ë¤ìê³¼ ê°ì ìí©ì ìì¸¡í´ ë³¼ ì ììµëë¤.
- Jwtí í° ìì´ api를 í¸ì¶íìì ê²½ì°
- íìì ë§ì§ ìê±°ë ë§ë£ë Jwtí í°ì¼ë¡ api를 í¸ì¶í ê²½ì°
- Jwtí í°ì¼ë¡ api를 í¸ì¶íìì¼ë í´ë¹ 리ìì¤ì ëí ê¶íì´ ìë ê²½ì°
1,2 ë²ì ë¤ìê³¼ ê°ì ì¤ë¥ê° ë°ìí©ëë¤.

3ë²ì ë¤ìê³¼ ê°ì ì¤ë¥ê° ë°ìí©ëë¤.

커ì¤í ìì¸ ì²ë¦¬ê° ì ì©ì´ ìëë ì´ì
ìì ìí©ìì 커ì¤í ì¼ë¡ ì ì©í ìì¸ ì²ë¦¬ê° ì ì©ì´ ì ëë ì´ì ë íí°ë§ì ìì ë문ì ëë¤. ì§ê¸ê¹ì§ ì ì©í 커ì¤í ìì¸ì²ë¦¬ë ControllerAdvice ì¦ Springì´ ì²ë¦¬ ê°ë¥í ììê¹ì§ 리íì¤í¸ê° ëë¬í´ì¼ ì²ë¦¬í ì ììµëë¤. ê·¸ë¬ë SpringSecurityë Spring ìë¨ìì íí°ë§ì í기 ë문ì, í´ë¹ ìí©ì exeptionì´ Springì DispatcherServletê¹ì§ ëë¬íì§ ìê² ë©ëë¤.
1,2 ë²ì ëí í´ê²°ì±
ì¨ì í Jwtê° ì ë¬ì´ ìë ê²½ì°ë í í° ì¸ì¦ ì²ë¦¬ ìì²´ê° ë¶ê°ë¥í기 ë문ì, í í° ê²ì¦ ë¨ìì íë¡ì¸ì¤ê° ëëë²ë¦¬ê² ë©ëë¤. í´ë¹ ìì¸ë¥¼ ì¡ìë´ë ¤ë©´ SpringSecurityìì ì ê³µíë AuthenticationEntryPoint를 ììë°ì ì¬ì ì í´ì¼ í©ëë¤. ìì¸ê° ë°ìí ê²½ì° ìëììë /exception/entrypointë¡ í¬ìë©ëëë¡ ì²ë¦¬íììµëë¤.
CustomAuthenticationEntryPoint ìì±
package com.rest.api.config.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex) throws IOException,
ServletException {
response.sendRedirect("/exception/entrypoint");
}
}
ExceptionController ìì±
controller packageíìì exception package를 ìì±íê³ ExceptionController를 ìì±í©ëë¤. ë´ì©ì /exception/entrypointë¡ ì£¼ìê° ë¤ì´ì¤ë©´ CAuthenticationEntryPointExceptionì ë°ììí¤ë¼ë ê²ì ëë¤.
package com.rest.api.controller.exception;
// import ìëµ
@RequiredArgsConstructor
@RestController
@RequestMapping(value = "/exception")
public class ExceptionController {
@GetMapping(value = "/entrypoint")
public CommonResult entrypointException() {
throw new CAuthenticationEntryPointException();
}
}
CAuthenticationEntryPointException ìì±
package com.rest.api.advice.exception;
public class CAuthenticationEntryPointException extends RuntimeException {
public CAuthenticationEntryPointException(String msg, Throwable t) {
super(msg, t);
}
public CAuthenticationEntryPointException(String msg) {
super(msg);
}
public CAuthenticationEntryPointException() {
super();
}
}
Message ë´ì© ì¶ê°
// exception_ko.yml entryPointException: code: "-1002" msg: "í´ë¹ 리ìì¤ì ì ê·¼í기 ìí ê¶íì´ ììµëë¤." // exception_en.yml entryPointException: code: "-1002" msg: "You do not have permission to access this resource.
ExceptionAdvice ë´ì© ì¶ê°
@ExceptionHandler(CAuthenticationEntryPointException.class)
public CommonResult authenticationEntryPointException(HttpServletRequest request, CAuthenticationEntryPointException e) {
return responseService.getFailResult(Integer.valueOf(getMessage("entryPointException.code")), getMessage("entryPointException.msg"));
}
SpringSecurityConfigurationì CustomAuthenticationEntryPoint ì¤ì ì¶ê°
.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())를 ì¶ê°íê³ /exception주ì를 PermitAll()ì ì¶ê°íë¤.
@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("/*/signin", "/*/signup").permitAll() // ê°ì
ë° ì¸ì¦ 주ìë ë구ë ì ê·¼ê°ë¥
.antMatchers(HttpMethod.GET, "/exception/**", "helloworld/**").permitAll() // hellowworldë¡ ììíë GETìì² ë¦¬ìì¤ë ë구ë ì ê·¼ê°ë¥
.anyRequest().hasRole("USER") // ê·¸ì¸ ëë¨¸ì§ ìì²ì 모ë ì¸ì¦ë íìë§ ì ê·¼ ê°ë¥
.and()
.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); // jwt token íí°ë¥¼ id/password ì¸ì¦ íí° ì ì ë£ì´ë¼.
}
Swagger Test

3ë²ì ëí í´ê²°ì±
3ë²ì Jwtí í°ì ì ìì´ë¼ë ê°ì íì Jwtí í°ì´ ê°ì§ì§ 못í ê¶íì 리ìì¤ë¥¼ ì ê·¼í ë ë°ìíë ì¤ë¥ì ëë¤. ì´ ê²½ì°ìë SpringSecurityìì ì ê³µíë AccessDeniedHandler를 ììë°ì 커ì¤í° ë§ì´ì§ í´ì¼ í©ëë¤. ìì¸ê° ë°ìí ê²½ì° handlerììë /exception/accessdeniedë¡ í¬ìë©ëëë¡ íììµëë¤.
CustomAccessDeniedHandler ìì±
package com.rest.api.config.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException,
ServletException {
response.sendRedirect("/exception/accessdenied");
}
}
ExceptionController ìì
ììì ìì±í ExceptionControllerì ë¤ì ë´ì©ì ì¶ê°í©ëë¤. ì´ë¯¸ ì¡´ì¬íë Exceptionì´ë¯ë¡ 커ì¤í Exceptionì ë°ë¡ ë§ë¤ì§ ìê³ ê¸°ì¡´ AccessDeniedExceptionì ë°ììíµëë¤.
@GetMapping(value = "/accessdenied")
public CommonResult accessdeniedException() {
throw new AccessDeniedException("");
}
Message ë´ì© ì¶ê°
// exception_ko.yml accessDenied: code: "-1003" msg: "ë³´ì í ê¶íì¼ë¡ ì ê·¼í ì ìë 리ìì¤ ì ëë¤." // exception_en.yml accessDenied: code: "-1003" msg: "A resource that can not be accessed with the privileges it has."
ExceptionAdvice ë´ì© ì¶ê°
@ExceptionHandler(AccessDeniedException.class)
public CommonResult AccessDeniedException(HttpServletRequest request, AccessDeniedException e) {
return responseService.getFailResult(Integer.valueOf(getMessage("accessDenied.code")), getMessage("accessDenied.msg"));
}
SpringSecurityConfigurationì CustomAccessDeniedHandler ì¤ì ì¶ê°
í
ì¤í¸ë¥¼ ìí´ /users apië ROLE_ADMIN ê¶íë§ ì ê·¼ ê°ë¥íê² ì²ë¦¬í´ ëìµëë¤.
.antMatchers(“/*/users”).hasRole(“ADMIN”)
@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("/*/signin", "/*/signup").permitAll() // ê°ì
ë° ì¸ì¦ 주ìë ë구ë ì ê·¼ê°ë¥
.antMatchers(HttpMethod.GET, "/exception/**", "helloworld/**").permitAll() // hellowworldë¡ ììíë GETìì² ë¦¬ìì¤ë ë구ë ì ê·¼ê°ë¥
.antMatchers("/*/users").hasRole("ADMIN")
.anyRequest().hasRole("USER") // ê·¸ì¸ ëë¨¸ì§ ìì²ì 모ë ì¸ì¦ë íìë§ ì ê·¼ ê°ë¥
.and()
.exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler())
.and()
.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); // jwt token íí°ë¥¼ id/password ì¸ì¦ íí° ì ì ë£ì´ë¼.
}
Swagger Test

ìì¸ ì²ë¦¬ ê³ ëíê¹ì§ ì ì ì©ëììì íì¸íììµëë¤. ì´ì ìë²ë ì´ë ì ëì ë³´ìì±ì ê°ì¶ê² ëììµëë¤. SpringSecurity를 ì ì©íì§ ìê³ ëì¼í íë¡ì¸ì¤ë¥¼ ì²ìë¶í° ë§ë¤ë ¤ê³ íì¼ë©´, ë§ì ê³ ëê³¼ ìì¸ìí©ì ëí ëì²ë¥¼ ìí´ ìë§ì ìê°ê³¼ ë ¸ë ¥ì´ íìíì ê²ì ëë¤. ê·¸ë ì§ë§ SpringSecurity를 ì ì©íëë°ë ìë¹í ì§ìê³¼ ë ¸ë ¥ì´ íìí©ëë¤. 본문ì ëì¨ ë´ì©ì Securityì ì¼ë¶ ë´ì©ì¼ ë¿ì ëë¤. ê·¸ë§í¼ SpringSecurity ë´ì©ì ë°©ëí©ëë¤. ì´ ê¸ì íµí´ ìë²ë¥¼ ë³´ë¤ ë ê²¬ê³ íê² ë§ëë 첫걸ìì´ ëìì¼ë©´ ì¢ê² ìµëë¤.
ì¶ê°) annotationì¼ë¡ 리ìì¤ ì ê·¼ ê¶í ì¤ì
ì¤ìµììë 리ìì¤ì ëí ì ê·¼ê¶í ì¤ì ì ìëì ê°ì´ SecurityConfiguration.javaì configure(HttpSecurity http) ë©ìë ë´ë¶ìì authorizeRequests()를 íµí´ ì¸í íììµëë¤. ì´ë ê² íë©´ 리ìì¤ì ê¶íì ì¤ìê´ë¦¬íë¤ë ì ìì ì´ì ì´ ìëë°ì. ì¶ê°ë¡ Springììë annotationì¼ë¡ë ê¶í ì¤ì ì´ ê°ë¥íëë¡ ì§ìíê³ ììµëë¤. ê°ê°ì ë°©ìì ì¥ë¨ì ì´ ìì¼ë¯ë¡ ìí©ì ë°ë¼ ì í©í ë°©ë²ì ì±íí´ ì¬ì©íë©´ ë ê² ê°ìµëë¤.
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// ìëµ...
.and()
.authorizeRequests() // ë¤ì 리íì¤í¸ì ëí ì¬ì©ê¶í ì²´í¬
.antMatchers("/*/signin", "/*/signin/**", "/*/signup", "/*/signup/**", "/social/**").permitAll() // ê°ì
ë° ì¸ì¦ 주ìë ë구ë ì ê·¼ê°ë¥
.antMatchers(HttpMethod.GET, "/exception/**", "/helloworld/**","/actuator/health", "/v1/board/**", "/favicon.ico").permitAll() // ë±ë¡ë GETìì² ë¦¬ìì¤ë ë구ë ì ê·¼ê°ë¥
.anyRequest().hasRole("USER") // ê·¸ì¸ ëë¨¸ì§ ìì²ì 모ë ì¸ì¦ë íìë§ ì ê·¼ ê°ë¥
// ìëµ...
}
@PreAuthorize, @Secured
ê¶í ì¤ì ì´ íìí 리ìì¤(ì¤ìµììë Controllerì ê° ë©ìëì í´ë¹)ì @PreAuthorize, @Securedë¡ ê¶íì ì¸í í ì ììµëë¤. ëë¤ ê°ì ìí ì íì§ë§ ìëì ê°ì ì°¨ì´ê° ììµëë¤.
@PreAuthorize
ííì ì¬ì© ê°ë¥
ex) @PreAuthorize(“hasRole(‘ROLE_USER’) and hasRole(‘ROLE_ADMIN’)”)
@Secured
ííì ì¬ì© ë¶ê°ë¥
ex) @Secured({“ROLE_USER”,”ROLE_ADMIN”})
annotationì¼ë¡ ê¶íì¤ì ì íë ¤ë©´ GlobalMethodSecurity를 íì±íí´ì¼ í©ëë¤. ìëì ê°ì´ SecurityConfiguration ìë¨ì @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)를 ì¶ê°í©ëë¤. ê·¸ë¦¬ê³ configure ë©ìë ììì authorizeRequest() ì¤ì ì 주ì ì²ë¦¬íê±°ë ìì í©ëë¤.
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@RequiredArgsConstructor
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
ìëµ...
@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("/*/signin", "/*/signin/**", "/*/signup", "/*/signup/**", "/social/**").permitAll() // ê°ì
ë° ì¸ì¦ 주ìë ë구ë ì ê·¼ê°ë¥
// .antMatchers(HttpMethod.GET, "/exception/**", "/helloworld/**","/actuator/health", "/v1/board/**", "/favicon.ico").permitAll() // ë±ë¡ë GETìì² ë¦¬ìì¤ë ë구ë ì ê·¼ê°ë¥
// .anyRequest().hasRole("USER") // ê·¸ì¸ ëë¨¸ì§ ìì²ì 모ë ì¸ì¦ë íìë§ ì ê·¼ ê°ë¥
.and()
.exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler())
.and()
.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); // jwt token íí°ë¥¼ id/password ì¸ì¦ íí° ì ì ë£ì´ë¼.
}
ìëµ ...
}
Controllerì ê¶íì ì¤ì í©ëë¤. ë§ì½ Controller ë´ë¶ì 모ë 리ìì¤ì ëíì¬ ì¼ê´ë¡ ëì¼í ê¶íì ì¤ì í ê²ì´ë©´ Controller ìë¨ì annotationì ì¸í í©ëë¤.
@PreAuthorize("hasRole('ROLE_USER')") ëë @Secured("ROLE_USER")
@Api(tags = {"2. User"})
@RequiredArgsConstructor
@RestController
@RequestMapping(value = "/v1")
public class UserController {
ë´ì© ìëµ....
}
ë§ì½ ê°ê°ì 리ìì¤ë§ë¤ ê¶íì ì¤ì í´ì¼ íë©´ í´ë¹ ë©ìë ìì annotationì ì¸í íë©´ ë©ëë¤.
@Api(tags = {"2. User"})
@RequiredArgsConstructor
@RestController
@RequestMapping(value = "/v1")
public class UserController {
private final UserJpaRepo userJpaRepo;
private final ResponseService responseService; // 결과를 ì²ë¦¬í Service
@Secured("ROLE_USER")
@ApiImplicitParams({
@ApiImplicitParam(name = "X-AUTH-TOKEN", value = "ë¡ê·¸ì¸ ì±ê³µ í access_token", required = true, dataType = "String", paramType = "header")
})
@ApiOperation(value = "íì 리ì¤í¸ ì¡°í", notes = "모ë íìì ì¡°ííë¤")
@GetMapping(value = "/users")
public ListResult<User> findAllUser() {
// ê²°ê³¼ë°ì´í°ê° ì¬ë¬ê±´ì¸ê²½ì° getListResult를 ì´ì©í´ì 결과를 ì¶ë ¥íë¤.
return responseService.getListResult(userJpaRepo.findAll());
}
@PreAuthorize("hasRole('ROLE_USER')")
@ApiImplicitParams({
@ApiImplicitParam(name = "X-AUTH-TOKEN", value = "ë¡ê·¸ì¸ ì±ê³µ í access_token", required = true, dataType = "String", paramType = "header")
})
@ApiOperation(value = "íì ë¨ê±´ ì¡°í", notes = "íìë²í¸(msrl)ë¡ íìì ì¡°ííë¤")
@GetMapping(value = "/user")
public SingleResult<User> findUser() {
// SecurityContextìì ì¸ì¦ë°ì íìì ì 보를 ì»ì´ì¨ë¤.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String id = authentication.getName();
// ê²°ê³¼ë°ì´í°ê° ë¨ì¼ê±´ì¸ê²½ì° getSingleResult를 ì´ì©í´ì 결과를 ì¶ë ¥íë¤.
return responseService.getSingleResult(userJpaRepo.findByUid(id).orElseThrow(CUserNotFoundException::new));
}
ìëµ...
}
ììì annotationì¼ë¡ ê¶íì ì¤ì í 리ìì¤ ì¸ ëë¨¸ì§ ë¦¬ìì¤ë¤ì ë구ë ì ê·¼ ê°ë¥í 리ìì¤ë¡ ì¤ì ë©ëë¤.
ìµì ìì¤ë GitHub ì¬ì´í¸ë¥¼ ì°¸ê³ í´ ì£¼ì¸ì.
https://github.com/codej99/SpringRestApi/tree/feature/security
GitHub Repository를 importíì¬ Intellij íë¡ì í¸ë¥¼ 구ì±íë ë°©ë²ì ë¤ì í¬ì¤í ì ì°¸ê³ í´ì£¼ì¸ì.
Dockerë¡ ê°ë° íê²½ì ë¹ ë¥´ê² êµ¬ì¶íë ê²ë ê°ë¥í©ëë¤. ë¤ì ë¸ë¡ê·¸ ë´ì©ì ì½ì´ë³´ì¸ì!
ì¤íë§ api ìë²ë¥¼ ì´ì©íì¬ ì¹ì¬ì´í¸ë¥¼ ë§ë¤ì´ë³´ê³ ì¶ì¼ìë©´ ìë í¬ì¤í ì ì°¸ê³ í´ ì£¼ì¸ì.













