MVN Repository > jjwt 검색
JJWT::API , JJWT::Impl , JJWT:: Jackson 설치
MVN Repository > spring boot security 검색
Table 생성
user_mst
role_mst
authority_mst
query문 작성
select
*
from
user_mst um
left outer join authority_mst am on(am.user_id = um.user_id)
left outer join role_mst rm on(rm.role_id = am.role_id)
User
package com.web.study.domain.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
private int user_id;
private String username;
private String password;
private String name;
private String email;
}
Authority
package com.web.study.domain.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Authority {
private int authority_id;
private int user_id;
private int role_id;
}
Role
package com.web.study.domain.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Role {
private int role_id;
private String role_name;
}
UserRepository
package com.web.study.repository;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.web.study.domain.entity.Authority;
import com.web.study.domain.entity.User;
@Mapper
public interface UserRepository {
public int saveUser (User user);
public int addAuthorities(List<Authority> authorities);
}
user.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.web.study.repository.UserRepository">
<insert id="saveUser"
parameterType="com.web.study.domain.entity.User"
useGeneratedKeys="true"
keyProperty="user_id">
insert into user_mst
value(0, #{username}, #{password}, #{name}, #{email})
</insert>
<insert id="addAuthorities" parameterType="list">
insert into authority_mst
values
<foreach collection="list" item="authority" separator=",">
(0,#{authority.user_id},#{authority.role_id})
</foreach>
</insert>
</mapper>
package com.web.study.service;
import com.web.study.dto.request.auth.RegisteUserReqDto;
public interface AuthService {
public void registeUser(RegisteUserReqDto registeUserReqDto );
}
}
package com.web.study.dto.request.auth;
import com.web.study.domain.entity.User;
import lombok.Data;
@Data
public class RegisteUserReqDto {
private String username;
private String password;
private String name;
private String email;
public User toEntity() {
return User.builder()
.username(username)
.password(password)
.name(name)
.email(email)
.build();
}
}
PASSWORD 암호화
package com.web.study.dto.request.auth;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import com.web.study.domain.entity.User;
import lombok.Data;
@Data
public class RegisteUserReqDto {
private String username;
private String password;
private String name;
private String email;
public User toEntity() {
return User.builder()
.username(username)
.password(new BCryptPasswordEncoder().encode(password))
.name(name)
.email(email)
.build();
}
}
package com.web.study.service;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.web.study.domain.entity.Authority;
import com.web.study.domain.entity.User;
import com.web.study.dto.request.auth.RegisteUserReqDto;
import com.web.study.repository.UserRepository;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {
private final UserRepository userRepository;
@Override
public void registeUser(RegisteUserReqDto registeUserReqDto) {
User userEntity = registeUserReqDto.toEntity();
userRepository.saveUser(userEntity); // insert user_mst
List<Authority> authorities = new ArrayList<>();
authorities.add(Authority.builder().user_id(userEntity.getUser_id()).role_id(1).build());
userRepository.addAuthorities(authorities);
}
}
package com.web.study.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// security filter
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/auth/register/**", "/auth/login/**")
.permitAll()
.anyRequest()
.authenticated();
}
}
CSRF(Cross - Site Request Forgery는 웹 사이트의 취약점을 이용하여 사용자가 신뢰할 수 있는 사이트에서 악의적인 요청을 보내도록 하는 공격 방법, 사용자의 인증 정보를 이용하여 공격자가 사용자 몰래 원하지 않는 행위를 수행하게 만드는데, 이과정에서 사용자의 인증 토큰, 쿠키 등을 이용함
웹 사이트는 이러한 CSRF
공격을 막기 위해 다양한 방법을 사용
CSRF
공격을 방어하기 위한 기능이 기본적으로 활성화되어 있음package com.web.study.controller.auth;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.web.study.dto.DataResponseDto;
import com.web.study.dto.ResponseDto;
import com.web.study.dto.request.auth.LoginReqDto;
import com.web.study.dto.request.auth.RegisteUserReqDto;
import com.web.study.service.AuthService;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
@PostMapping("/auth/register")
public ResponseEntity<? extends ResponseDto> registe(@RequestBody RegisteUserReqDto registeUserReqDto) {
authService.duplicatedUsername(registeUserReqDto);
authService.registeUser(registeUserReqDto);
return ResponseEntity.ok().body(ResponseDto.ofDefault());
}
@PostMapping("/auth/login")
public ResponseEntity<? extends ResponseDto> login(@RequestBody LoginReqDto loginReqDto) {
authService.login(loginReqDto);
return ResponseEntity.ok().body(DataResponseDto.of(authService.login(loginReqDto)));
}
}
{
"username" : "abc",
"password" : "1234",
"name" : "김준일",
"email" : "abc@gmail.com"
}
DB에 회원가입 추가
user.xml 추가
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.web.study.repository.UserRepository">
<resultMap type="com.web.study.domain.entity.Role" id="role">
<result column="role_id" property="role_id"/>
<result column="role_name" property="role_name"/>
</resultMap>
<resultMap type="com.web.study.domain.entity.Authority" id="authority">
<result column="authority_id" property="authority_id"/>
<result column="user_id" property="user_id"/>
<result column="role_id" property="role_id"/>
<collection property="role" resultMap="role"/>
</resultMap>
<resultMap type="com.web.study.domain.entity.User" id="user">
<result column="user_id" property="user_id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="name" property="name"/>
<result column="email" property="email"/>
<collection property="authorities" javaType="list" resultMap="authority"></collection>
</resultMap>
<insert id="saveUser"
parameterType="com.web.study.domain.entity.User"
useGeneratedKeys="true"
keyProperty="user_id">
insert into user_mst
value(0, #{username}, #{password}, #{name}, #{email})
</insert>
<insert id="addAuthorities" parameterType="list">
insert into authority_mst
values
<foreach collection="list" item="authority" separator=",">
(0,#{authority.user_id},#{authority.role_id})
</foreach>
</insert>
<select id="findUserByUsername" resultMap="user">
select
um.user_id,
um.username,
um.password,
um.name,
um.email,
am.authority_id,
am.role_id,
rm.role_name
from
user_mst um
left outer join authority_mst am on(am.user_id = um.user_id)
left outer join role_mst rm on(rm.role_id = am.user_id)
where
um.username = #{username}
</select>
</mapper>
AuthServiceImpl 수정
package com.web.study.service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;
import com.web.study.domain.entity.Authority;
import com.web.study.domain.entity.User;
import com.web.study.dto.request.auth.RegisteUserReqDto;
import com.web.study.exception.CustomException;
import com.web.study.repository.UserRepository;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {
private final UserRepository userRepository;
@Override
public void registeUser(RegisteUserReqDto registeUserReqDto) {
User userEntity = registeUserReqDto.toEntity();
userRepository.saveUser(userEntity); // insert user_mst
List<Authority> authorities = new ArrayList<>();
authorities.add(Authority.builder().user_id(userEntity.getUser_id()).role_id(1).build());
userRepository.addAuthorities(authorities);
}
@Override
public void duplicatedUsername(RegisteUserReqDto registeUserReqDto) {
User userEntity = userRepository.findUserByUsername(registeUserReqDto.getUsername());
if(userEntity != null) {
Map<String, String> errorMap = new HashMap<>();
errorMap.put("username", "이미 사용중인 사용자이름입니다.");
throw new CustomException("중복 검사 오류", errorMap);
}
}
}
AuthController 수정
package com.web.study.controller.auth;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.web.study.dto.ResponseDto;
import com.web.study.dto.request.auth.RegisteUserReqDto;
import com.web.study.service.AuthService;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
@PostMapping("/auth/register")
public ResponseEntity<? extends ResponseDto> registe(@RequestBody RegisteUserReqDto registeUserReqDto) {
authService.duplicatedUsername(registeUserReqDto);
authService.registeUser(registeUserReqDto);
return ResponseEntity.ok().body(ResponseDto.ofDefault());
}
}
결과
AuthController 수정
코드를 입력하세요
package com.web.study.dto.request.auth;
import lombok.Data;
@Data
public class LoginReqDto {
private String username;
private String password;
}
AuthService 수정
package com.web.study.service;
import com.web.study.dto.request.auth.LoginReqDto;
import com.web.study.dto.request.auth.RegisteUserReqDto;
import com.web.study.dto.response.auth.JwtTokenRespDto;
public interface AuthService {
public void registeUser(RegisteUserReqDto registeUserReqDto );
public void duplicatedUsername(RegisteUserReqDto registeUserReqDto);
public JwtTokenRespDto login(LoginReqDto loginReqDto);
}
package com.web.study.dto.response.auth;
import lombok.Builder;
import lombok.Data;
@Builder
@Data
public class JwtTokenRespDto {
private String grantType;
private String accessToken;
}
package com.web.study.security;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.web.study.repository.UserRepository;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class PrincipalDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("로그인 시도 감지");
System.out.println("username: " + username);
return null;
}
}
POSTMAN 로그인 post요청
UserDetailsService의 loadUserByUsername() 호출
package com.web.study.security;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.web.study.domain.entity.User;
import com.web.study.exception.CustomException;
import com.web.study.repository.UserRepository;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class PrincipalDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User userEntity = userRepository.findUserByUsername(username);
if(userEntity == null) {
throw new CustomException("사용자 정보를 다시 확인해주세요.");
}
return null;
}
}
등록되지않은 id 검색 시
entity > User 수정
package com.web.study.domain.entity;
import java.util.ArrayList;
import java.util.List;
import com.web.study.security.PrincipalUserDatails;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
private int user_id;
private String username;
private String password;
private String name;
private String email;
private List<Authority> authorities;
public PrincipalUserDatails toPrincipal() {
List<String> roles = new ArrayList<>();
authorities.forEach(authority ->{
roles.add(authority.getRole().getRole_name());
});
return PrincipalUserDatails.builder()
.userId(user_id)
.username(username)
.password(password)
.roles(roles)
.build();
}
}
package com.web.study.security;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import lombok.Builder;
@Getter
@Builder
public class PrincipalUserDetails implements UserDetails {
package com.web.study.security;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import lombok.Builder;
import lombok.Getter;
@Builder
@Getter
public class PrincipalUserDetails implements UserDetails {
private static final long serialVersionUID = 2482068462613165077L;
private int userId;
private String username;
private String password;
private List<String> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authority = new ArrayList<>();
roles.forEach(role -> {
authority.add(new SimpleGrantedAuthority(role));
});
return authority;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
// 사용 기간 만료
@Override
public boolean isAccountNonExpired() {
return true;
}
/// 계정을 잠궈버림
@Override
public boolean isAccountNonLocked() {
return true;
}
// 비밀번호 5회 틀렸을 때
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 계정 비활성 상태(이메일 인증을 완료해야하거나, 전화번호 인증을 하지 않았을 때)
@Override
public boolean isEnabled() {
return true;
}
}
application.yml 추가
jwt:
secretKey : pwGPHexO93sRZzfKNlMg1cLsHJHoRb66pwGPHexO93sRZzfKNlMg1cLsHJHoRb66
JwtTokenProvider
package com.web.study.security.jwt;
import java.security.Key;
import java.util.Date;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import com.web.study.dto.response.auth.JwtTokenRespDto;
import com.web.study.security.PrincipalUserDetails;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
@Component
public class JwtTokenProvider {
private final Key key;
public JwtTokenProvider(@Value("${jwt.secretKey}") String secretKey) {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
this.key = Keys.hmacShaKeyFor(keyBytes);
}
public JwtTokenRespDto creatToken(Authentication authentication) {
StringBuilder authoritiesBuilder = new StringBuilder();
authentication.getAuthorities().forEach(grantedcAuthority -> {
authoritiesBuilder.append(grantedcAuthority.getAuthority());
authoritiesBuilder.append(",");
});
authoritiesBuilder.delete(authoritiesBuilder.length() - 1, authoritiesBuilder.length());
String authorities = authoritiesBuilder.toString();
long now = (new Date()).getTime();
// 1000 == 1초
Date tokenExpiresDate = new Date(now + (1000 * 60 * 300)); // 토큰 만료 시간 ( 현재 시간 + Timer)
PrincipalUserDetails userDetails = (PrincipalUserDetails) authentication.getPrincipal();
String accessToken = Jwts.builder()
.setSubject(authentication.getName())
.claim("userId", userDetails.getUserId())
.claim("auth", authorities)
.setExpiration(tokenExpiresDate)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
return JwtTokenRespDto.builder()
.grantType("Bearer")
.accessToken(accessToken)
.build();
}
}