46. 로그인
-로그인
*서비스 생성(MemberDetailsService.java)
package com.example.service;
import java.util.Collection;
import com.example.entity.Member;
import com.example.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
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;
//시큐리티에서 로그인 시 UserDetailsService 인터페이스의
//메소드 loadUserByUsername이 자동으로 호출됨
//여기에 필요한 기능을 구현하고 User를 리턴 시킴
@Service
public class MemberDetailsService implements UserDetailsService{
@Autowired
MemberRepository mRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Member member = mRepository.findById(username).get();
//String의 권한을 collection<>으로 변환
String[] role = {member.getRole()};
Collection<GrantedAuthority> roles = AuthorityUtils.createAuthorityList(role);
//이메일, 암호, 권한 정보를 리턴
//import org.springframework.security.core.userdetails.User;
User user = new User(member.getEmail(), member.getPasswd(), roles);
return user;
}
}
-jwt 추가(토큰 생성 및 유효성 확인)
*JwtUtil.java(토큰 생성, 유효성 확인)
package com.example.jwt;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.springframework.stereotype.Service;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
//토큰발행(만료시간), 토크의 유효성검사, 토큰과 관련된 메소드
@Service
public class JwtUtil {
//토큰 생성용 보안키
private final String SECRETKEY = "asdfasdg";
// 정보 추출용 메소드
private <T> T extractClaim(String token, Function<Claims, T> claimsResolver){
final Claims claims = Jwts.parser().setSigningKey(SECRETKEY).parseClaimsJws(token).getBody();
return claimsResolver.apply(claims);
}
//토큰생성(아이디 정보를 이용한 토큰 생성)
public String generateToken(String username){
//2021-09-30 09:47:50.000 => 1234567890 => 1234567890123
// long tokenValidTime = 1000 * 60 * 30; //30분
long tokenValidTime = 1000 * 60 * 60 * 4; //4시간
Map<String, Object> claims = new HashMap<>();
String token = Jwts.builder()
.setClaims(claims)
.setSubject(username)
.setIssuedAt(new Date(System.currentTimeMillis() ) ) //현재시간
.setExpiration(new Date(System.currentTimeMillis() + tokenValidTime ) ) // 만료시간
.signWith(SignatureAlgorithm.HS256, SECRETKEY)
.compact();
return token;
}
//토큰유효성 확인
// vue에서 오는 토큰에서 꺼낸 아이디 정보와
// 시큐리티 세션에 보관되어 있던 아이디 정보
public Boolean validateToken(String token, String userid){
//토큰에서 아이디 정보 추출
final String username = this.extractUsername(token);
// vue에서 오는 토큰에서 꺼낸 아이디 정보와
// 시큐리티 세션에 보관되어 있던 아이디 정보
if(username.equals(userid) && !isTokenExpired(token) ){
return true;
}
return false;
}
//토큰에서 아이디 정보 추출하기
public String extractUsername(String token){
return extractClaim(token, Claims::getSubject);
}
//토큰에서 만료 시간 추출하기
public Date extractExpiration(String token){
return extractClaim(token, Claims::getExpiration);
}
//토큰의 만료시간이 유효한지 확인
public Boolean isTokenExpired(String token){
//만료시간 가져와서 현재시간보다 이전인지 확인
return this.extractExpiration(token).before(new Date());
}
}
*JwtRequestFillter.java(토큰 확인)
package com.example.jwt;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.example.service.MemberDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
//nodejs checkToken
//인증서가 유효하면
@Component
public class JwtRequestFillter extends OncePerRequestFilter {
@Autowired
JwtUtil jwtUtil;
@Autowired
MemberDetailsService mService;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
try{
//const headers = {"Content-Type":"application/json", "token":'123456_aaa'}
String token = request.getHeader("token");
String username =null;
if(token != null ) {
//실제 토큰
username = jwtUtil.extractUsername(token);
}
//username과 시큐리티 세션에 있는 사용자 아이디와 비교
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
//토큰은 전달외었고 로그인이 되어야 되는 것만
if(username != null && auth == null ) {
//시큐리티에 로그인 처리 루틴
UserDetails userDetails = mService.loadUserByUsername(username);
if(jwtUtil.validateToken(token, userDetails.getUsername()) ){
UsernamePasswordAuthenticationToken
upat = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
upat.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request) );
SecurityContextHolder.getContext().setAuthentication(upat);
}
}
//컨트롤러로 넘어가는 시점
//next(req, res)
//토큰 유무와 관련없이 컨트롤러로 넘겨
filterChain.doFilter(request, response);
}
catch(Exception e) {
// status : 578, message : '토큰오류'
response.sendError(578, "토큰오류");
}
}
}
*db 연동
package com.example.security;
import com.example.jwt.JwtRequestFillter;
import com.example.service.MemberDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration //환경 설정 파일
@EnableWebSecurity //security를 적용
public class SecurityConfig extends WebSecurityConfigurerAdapter{
//새로운 기능을 추가한 DetailsService 사용
@Autowired
MemberDetailsService mService;
@Autowired
private JwtRequestFillter jwtRequestFillter;
//환경 설정에서 객체만들기
//회원가입시 암호 방식을 로그인 시 같은 방식으롤 적용해야 하기 때문에
@Bean
public BCryptPasswordEncoder encode(){
return new BCryptPasswordEncoder();
}
//인증방식은 MemberDetailsService를 사용하고,
//암호화 방식은 위에서 만든 @Bean 객체 방식으로 사용
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(mService).passwordEncoder(encode());
}
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//권한설정
http.authorizeRequests()
.antMatchers("/admin","/admin/*").hasAnyRole("ADMIN").anyRequest().permitAll();
// 필터 추가하기(@controller 전에 수행됨)
http.addFilterBefore(jwtRequestFillter, UsernamePasswordAuthenticationFilter.class);
//session 저장 방법
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
//h2 console 사용
//크롬에서 127.0.0.1:8080/REST/h2-console
http.csrf().disable();
http.headers().frameOptions().sameOrigin();
}
}
*/member/login post 생성(HomeController.java)
//로그인
//127.0.0.1:8080/REST/api/member/login
//{"email":"", "passwd":"", "name":"", "role":""}
@RequestMapping(value = "/member/login", method = {RequestMethod.POST},
consumes = MediaType.ALL_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public Map<String, Object> memberLoginPOST(
@RequestBody Member member) {
Map<String, Object> map = new HashMap<String, Object>();
try{
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(member.getEmail(), member.getPasswd()));
map.put("status", 200);
map.put("token", jwtUtil.generateToken(member.getEmail()));
}
catch(Exception e){
//e.printStackTrace();
map.put("status", e.hashCode());
}
return map;
}
*로그인(포스트맨)
*회원 가입과 마찬가지로 @RequestBody 를 사용했으므로 json 타입으로 입력한다.
*로그인 후 토큰 생성 확인
'Spring' 카테고리의 다른 글
Spring 일지 #48 (20211005) 일괄 추가1-1 (0) | 2021.10.06 |
---|---|
Spring 일지 #47 (20211005) 비밀번호 변경 (0) | 2021.10.05 |
Spring 일지 #45 (20211005) 회원가입 (0) | 2021.10.05 |
Spring 일지 #44 (20211005) db연동 (0) | 2021.10.05 |
Spring 일지 #43 (20211005) 환경 설정, 보안 해제 (0) | 2021.10.05 |