Spring

Spring 일지 #46 (20211005) 로그인

uni5948 2021. 10. 5. 17:57

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 usernamethrows UsernameNotFoundException {

        

        Member member = mRepository.findById(username).get();

        //String의 권한을 collection<>으로 변환

        String[] role = {member.getRole()};

        Collection<GrantedAuthorityroles = 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 <TT extractClaim(String tokenFunction<ClaimsTclaimsResolver){

        

        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<StringObjectclaims = new HashMap<>();

        String token = Jwts.builder()

            .setClaims(claims)

            .setSubject(username)

            .setIssuedAt(new Date(System.currentTimeMillis() ) ) //현재시간

            .setExpiration(new Date(System.currentTimeMillis() + tokenValidTime ) ) // 만료시간

            .signWith(SignatureAlgorithm.HS256SECRETKEY)

            .compact();

 

        return token;

    }

 

    //토큰유효성 확인

    // vue에서 오는 토큰에서 꺼낸 아이디 정보와

    // 시큐리티 세션에 보관되어 있던 아이디 정보

    public Boolean validateToken(String tokenString 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(tokenClaims::getSubject);

    }

 

    //토큰에서 만료 시간 추출하기

    public Date extractExpiration(String token){

        return extractClaim(tokenClaims::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 ServletExceptionIOException {

        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(tokenuserDetails.getUsername()) ){

                    UsernamePasswordAuthenticationToken

                    upat = new UsernamePasswordAuthenticationToken(

                        userDetailsnulluserDetails.getAuthorities());

                    upat.setDetails(

                        new WebAuthenticationDetailsSource().buildDetails(request) );

                    SecurityContextHolder.getContext().setAuthentication(upat);

                }

            }

            //컨트롤러로 넘어가는 시점

            //next(req, res)

            //토큰 유무와 관련없이 컨트롤러로 넘겨

            filterChain.doFilter(requestresponse);  

        }

        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 auththrows 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 httpthrows Exception {

        //권한설정

        http.authorizeRequests()

            .antMatchers("/admin","/admin/*").hasAnyRole("ADMIN").anyRequest().permitAll();

         // 필터 추가하기(@controller 전에 수행됨)

         http.addFilterBefore(jwtRequestFillterUsernamePasswordAuthenticationFilter.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<StringObjectmemberLoginPOST(

        @RequestBody Member member) {

        Map<StringObjectmap = new HashMap<StringObject>();

        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 타입으로 입력한다.

 *로그인 후 토큰 생성 확인

로그인 성공