Hyunseok
프로그래밍/개인홈페이지 :: Spring boot + JWT + OAuth2 + Redis로 Restful 로그인을 만들어보자 3
2022. 11. 28. 11:45

2022.11.28 - [프로그래밍/ㄴ 개인홈페이지] - :: Spring boot + JWT + OAuth2 + Redis로 Restful 로그인을 만들어보자 2

 

:: Spring boot + JWT + OAuth2 + Vue + Redis 로그인을 만들어보자 2

이 글은 아래의 글의 2편입니다 2022.11.28 - [프로그래밍/ㄴ 개인홈페이지] - :: Spring boot + JWT + OAuth2 + Vue + Redis 로그인을 만들어보자 1 이번 편에서는 util들을 설정해보자 간단한게 JWT와 Redis용 유틸

hbyun.tistory.com

 

이 글은 저번 2편의 다음편입니다

 

 

내 생각에는 시큐리티는....

작은 것부터 큰 것을 만드는 게 좋으니 일단 로그인에 사용될 유저용 도메인 2개 만들어보자

일반용과 OAuth2 두 개를 작성해보자

 

 

0. 유저 공통

@Entity
@Getter
@Builder
public class Members extends DateEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long mid;

    @Column
    private boolean oauth;

    @Column(nullable = false)
    private String email;

    @Column(nullable = false)
    private String nickname;

    @Column(nullable = false)
    private String password;

    @OneToMany(mappedBy = "mid", fetch = FetchType.LAZY, orphanRemoval = true)
    private List<Reply> reply = new ArrayList<>();

    @ElementCollection(fetch = FetchType.EAGER)
    @Column
    private Set<Roles> roles = new HashSet<>();

    @Column
    private String userimg;

    @Column
    private boolean logged;
}

 

1. 로그인 - 일반 유저용

@Getter
@Setter
public class BlogCustomUser extends User{
    private Long mid;
    private String email;
    private String password;
    private String nickname;
    private String token;

    public BlogCustomUser(String email, String password, Long mid, String nickname, 
    Collection<? extends GrantedAuthority> authorities){
    
        super(email, password, authorities);
        this.email = email;
        this.password = password;
        this.mid = mid;
        this.nickname = nickname;
        
    }
}

 

2. 로그인 - OAuth2 유저용

@Getter
@Setter
public class BlogOAuthUser extends User implements OAuth2User {

    private Long mid;
    private String email;
    private String password;
    private String nickname;
    private String token;

    public BlogOAuthUser(String email, String password, Long mid, String nickname, 
    					Collection<? extends GrantedAuthority> authorities){
        super(email, password, authorities);
        this.email = email;
        this.password = password;
        this.mid = mid;
        this.nickname = nickname;
    }

    @Override
    public String getName() {
        return this.nickname;
    }

    @Override
    public Map<String, Object> getAttributes() {
        return null;
    }

(ROLE은 사용이 권장되나.. 거의 안 쓰니 그냥 null로.. )

 

 

그러고 나서 이 두 유저를 사용해줄 서비스 두 개를 만들어주자

 

1. BlogUserDetailsService (일반 유저용)

@RequiredArgsConstructor
@Service
public class BlogUserDetailsService implements UserDetailsService{
    private final MembersRepository mrepo;
    @Transactional
    @Override
    public UserDetails loadUserByUsername(String email) 
    				throws UsernameNotFoundException {
        Members member = mrepo
        	.findByEmail(email)
                .orElseThrow(()->new NoSuchElementException());
                        
        return new BlogCustomUser(member.getEmail(), 
        			member.getPassword(), 
                                member.getMid(), 
                                member.getNickname(), 
                                member.getRoles()
                                	.stream()
                                    .map(role-> new SimpleGrantedAuthority(role.name())).toList());
    }    
}

2. BlogOauthService + 성공 핸들러

@RequiredArgsConstructor
@Service
public class BlogOauthService extends DefaultOAuth2UserService {
    private final MembersRepository mrepo;
    private final PasswordEncoder pEncoder;
    Members member;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) 
    		throws OAuth2AuthenticationException {
        OAuth2User userinfo = super.loadUser(userRequest);
        mrepo.findByEmail(userinfo.getAttribute("email")).ifPresentOrElse(
                (v) -> member = v,
                () -> member = mrepo.save(Members.builder()
                        .email(userinfo.getAttribute("email"))
                        .nickname("판독용닉네임")
                        .password(pEncoder.encode("판독용비밀번호"))
                        .userimg(userinfo.getAttribute("picture"))
                        .oauth(true)
                        .roles(Roles.ROLE_USER)
                        .build()));

                        
        return new BlogOAuthUser(member.getEmail(), 
        		member.getPassword(), 
                member.getMid(), 
                member.getNickname(),
                member.getRoles()
                	.stream().map(v -> new SimpleGrantedAuthority(v.name())).toList());
    }

}
public class BlogOauthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    private final MembersRepository mrepo;
    private final JwtManager manager;

    @Autowired
    private RedisUtil rUtil;

    public BlogOauthSuccessHandler(JwtManager manager, MembersRepository mrepo){
        this.manager = manager;
        this.mrepo = mrepo;
    }



    @Override
    @Transactional
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        Members memberInfo = mrepo.findByEmail(authentication.getName()).get();
        Long mid = memberInfo.getMid();
        String rToken = manager.RefreshTokenGenerator(memberInfo.getMid(), memberInfo.getEmail());
        rUtil.setRefreshToken(rToken, mid);

        ResponseCookie cookie1 = ResponseCookie.from(프론트단에서쓰일쿠키정보1).domain("127.0.0.1").path("/").build();
        ResponseCookie cookie2 = ResponseCookie.from(프론트단에서쓰일쿠키정보2).domain("127.0.0.1").path("/").build();
        Cookie user = new Cookie(프론트단에서쓰일쿠키정보3);
        user.setDomain("127.0.0.1");
        user.setPath("/");
        
        response.addHeader("Set-Cookie", cookie1.toString());
        response.addHeader("Set-Cookie", cookie2.toString());
        response.addCookie(user);
        if(memberInfo.getNickname().equals(아까 써둔 판독용 문자열)){
            Cookie noneinituser = new Cookie(프론트단에서쓰일쿠키정보3-1);
            noneinituser.setDomain("127.0.0.1");
            noneinituser.setPath("/");
            response.addCookie(noneinituser);
            response.sendRedirect("http://127.0.0.1:8080/initoauth");
        }else{
            response.sendRedirect("http://127.0.0.1:8080/");
        }
        
        super.onAuthenticationSuccess(request, response, authentication);
    }
}

나중에 OAuth2 유저에 필요한 정보를 더 담기 위해서 판독용 요소를 조금 넣어놨다

 

굳이 로그인한 상태로 바로 쓰게 하려면

 

oauth2 request에서 받은

email과 name을 바로 써도 무방하다 

 

성공 핸들러에서는 리프래시 토큰을 만들어서 그냥 헤더에 넣어줬다

어차피 프론트에서 만드나 여기서 만드나 쿠키 굽는 건 똑같다

 

마지막으로 일반 로그인에도 쓸 로그인 필터도 작성해주자

 

1. 로그인 필터

@Log4j2
public class BlogFilterForLogin extends AbstractAuthenticationProcessingFilter {
    private JwtManager manager;
    
    @Autowired
    private RedisUtil rUtil;

    public BlogFilterForLogin(JwtManager manager) {
        super(new AntPathRequestMatcher("/login"));
        this.manager = manager;
        
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, 
    HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        JSONObject obj = new JSONObject();
        try {
            obj = (JSONObject) new JSONParser()
            .parse(StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8));
        } catch (Exception e) {
            log.info(e);
        }
        return getAuthenticationManager().authenticate(
        	new UsernamePasswordAuthenticationToken(
            	obj.get("email").toString(), obj.get("password").toString()
            )
        );
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, 
    	HttpServletResponse response, FilterChain chain,Authentication authResult) 
    	throws IOException, ServletException {
                String email = ((BlogCustomUser) authResult.getPrincipal()).getEmail();
                Long userNum = ((BlogCustomUser) authResult.getPrincipal()).getMid();
                String token = manager.AccessTokenGenerator(userNum, email);
                String rToken = manager.RefreshTokenGenerator(userNum, email);
                ObjectMapper mapper = new ObjectMapper();
                HashMap<String, Object> returnInfo = new HashMap<>();
                returnInfo.put("token", token);
                returnInfo.put("rToken", rToken);
                returnInfo.put("email", email);
                returnInfo.put("memberId", userNum);
                returnInfo.put("nickname", ((BlogCustomUser) authResult
                											.getPrincipal())
                                                            .getNickname());
                rUtil.setRefreshToken(rToken, userNum);
                response.setContentType("application/json;charset=utf-8");
                
                response
                .getOutputStream()
                .write(mapper.writeValueAsString(returnInfo)
                .getBytes());
    }
}

사실 succesfulAuthentication을 나눠서 다른 파일에 담는 게 더 코드가 보기 좋아지지만

 

작성할 때 뇌를 빼놓고 작성했고.. 그냥 이대로 놔두기로 했다

 

또 길어졌으니.. 다음 편으로.. 이제 마지막 편이다

 

2022.11.28 - [프로그래밍/ㄴ 개인홈페이지] - :: Spring boot + JWT + OAuth2 + Redis로 Restful 로그인을 만들어보자 完

 

:: Spring boot + JWT + OAuth2 + Redis로 Restful 로그인을 만들어보자 完

2022.11.28 - [프로그래밍/ㄴ 개인홈페이지] - :: Spring boot + JWT + OAuth2 + Vue + Redis 로그인을 만들어보자 3 3편에서 계속되는 내용입니다 이번편은 securityconfig을 설정해보자 1. Securityconfig @EnableWebSecurity @

hbyun.tistory.com

 


프로그래밍/개인홈페이지의 다른 글