스프링부트 버전 : 2.6.3
회원 정보 변경후 Controller 에서
다음과 같이 처리한다.
방법 1 . 비밀번호가 반드시 필요하다.
Authentication authentication= new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()); SecurityContextHolder.getContext().setAuthentication(authentication);
Controller
@PutMapping("/auth/user") public ResponseEntity updateUser(@RequestBody User user){ userService.updateUser(user); //여기서는 트랜잭션이 종료되기 때문에 DB에 값은 변경이 됐음. //하지만 세션값은 변경되지 않은 상태이기 때문에 우리가 직접 세션값을 변경해 줄 것임. //서비스에서 트랜잭션 완료후(즉DB 변경후) 컨트롤에서 실행해야 시큐리티 세션이 변경된다. Authentication authentication= new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()); SecurityContextHolder.getContext().setAuthentication(authentication); //버전업으로 다음 처럼 세션을 직접 접근해서 변경은 안된다. //session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext); return new ResponseEntity(1, HttpStatus.OK); }
service
public void updateUser(User requestUser) { //수정시에는 영속성 컨텍스트 User 오브젝트를 영속화시키고, 영속화된 User 오브젝트를 수정 //select 를 해서 User 오브젝트를 DB로 부터 가져오는 이유는 영속화를 하기 위해서! //영속화된 오브젝트를 변경하면 자동으로 DB에 update 문 잘려준다. User persistanceUser =userRepository.findById(requestUser.getId()).orElseThrow(EntityExistsException::new); //User persistanceUser =userRepository.getByUsername(requestUser.getUsername()); //getByUsername 를 해도 업데이트 처리 된다. 즉 User 객체만 변경되면 업데이트 처리된다. String rawPassword=requestUser.getPassword(); String encPassword=passwordEncoder.encode(rawPassword); persistanceUser.setPassword(encPassword); persistanceUser.setEmail(requestUser.getEmail()); //회원 수정 함수 종료시 ==서비스 종료 == 트랜잭션 종료 = commit 이 자동으로 된다. //영속화된 pseristance 객체의 변화가 감지되면 더티체킹이 되어 updatge 문을 날려줌. }
방법 2. 비밀번호가 필요없다.(권장)
다음과 같이 UserDetails 객체와 user 권한 정보만 변경해 주면 세션이 재설정 된다.
/** * 시큐리티 세션 재설정 * 시큐리티 세션 재설정은 서비스에서 트랜잭션이 종료된 후 실행되는 컨트롤에서 설정해야 한다. */ public void sessionReset(User user) { //유저 한명에 권한이 여러개 설정될수 있기 때문에 list 한다. ex)GUEST,USER ,MANAGER,ADMIN Collection authorities = new ArrayList<>(); authorities.add(() -> user.getRole().toString()); UserDetails userDetails=principalDetailService.loadUserByUsername(user.getUsername()); Authentication newAuthentication = new UsernamePasswordAuthenticationToken(userDetails, null, authorities); SecurityContextHolder.getContext().setAuthentication(newAuthentication); }
import java.util.ArrayList; import java.util.Collection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import com.cos.blog.config.auth.PrincipalDetailService; import com.cos.blog.model.User; import com.cos.blog.service.UserService; import lombok.RequiredArgsConstructor; @RestController @RequiredArgsConstructor public class UserApiController { private final Logger log=LoggerFactory.getLogger(this.getClass()); private final UserService userService; private final PrincipalDetailService principalDetailService; //회원 수정 @PutMapping("/auth/user") public ResponseEntity updateUser(@RequestBody User user){ userService.updateUser(user); //여기서는 트랜잭션이 종료되기 때문에 DB에 값은 변경이 됐음. //하지만 세션값은 변경되지 않은 상태이기 때문에 우리가 직접 세션값을 변경해 줄 것임. //서비스에서 트랜잭션 완료후(즉DB 변경후) 컨트롤에서 실행해야 시큐리티 세션이 변경된다. // Authentication authentication= new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()); // SecurityContextHolder.getContext().setAuthentication(authentication); // sessionReset(user); //버전업으로 다음 처럼 세션을 직접 접근해서 변경은 안된다. //session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext); return new ResponseEntity(1, HttpStatus.OK); } /** * 시큐리티 세션 재설정 * 시큐리티 세션 재설정은 서비스에서 트랜잭션이 종료된 후 실행되는 컨트롤에서 설정해야 한다. */ public void sessionReset(User user) { //유저 한명에 권한이 여러개 설정될수 있기 때문에 list 한다. ex)GUEST,USER ,MANAGER,ADMIN Collection authorities = new ArrayList<>(); authorities.add(() -> user.getRole().toString()); UserDetails userDetails=principalDetailService.loadUserByUsername(user.getUsername()); Authentication newAuthentication = new UsernamePasswordAuthenticationToken(userDetails, null, authorities); SecurityContextHolder.getContext().setAuthentication(newAuthentication); } }
참조 : PrincipalDetailService
import org.springframework.beans.factory.annotation.Autowired; 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 org.springframework.transaction.annotation.Transactional; import com.cos.blog.model.User; import com.cos.blog.repository.UserRepository; @Service //Bean 등록 @Transactional public class PrincipalDetailService implements UserDetailsService{ @Autowired private UserRepository userRepository; /**스프링이 로그인 요청을 가로챌 때, username, passwrod 변수 2개를 가로채는데 password 부분처리는 알아서 함. username (email) 이 DB 에 있는지만 확인해주면 됨. return */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //이메일경우 예 // User principal=userRepository.findByEmail(email); // if(principal==null){ // throw new UsernameNotFoundException(email); // } // UserDetails result= org.springframework.security.core.userdetails.User.builder() // .username(principal.getEmail()) // .password(principal.getPassword()) // .roles(principal.getRole().toString()) // .build(); User principal=userRepository.findByUsername(username).orElseThrow(()->{ return new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다. :" + username); }); UserDetails result= org.springframework.security.core.userdetails.User.builder() .username(principal.getUsername()) .password(principal.getPassword()) .roles(principal.getRole().toString()) .build(); return result; } //커스텀 로그인 방법 : UserDetails 상속받은 PrincipalDetails 생성후 로그인 처리 방법 /* @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User principal=userRepository.findByUsername(username).orElseThrow(()->{ return new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다. :" + username); }); if(principal == null) { return null; } return new PrincipalDetails(principal); } */ }
참조 : PrincipalDetails
import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.Map; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.oauth2.core.user.OAuth2User; import com.cos.blog.model.User; import groovy.transform.ToString; import lombok.Data; import lombok.NoArgsConstructor; @NoArgsConstructor @Data @ToString public class PrincipalDetails implements UserDetails, OAuth2User{ private static final long serialVersionUID = 1L; private User user; private Map attributes; //일반 로그인 public PrincipalDetails(User user) { this.user=user; } // OAuth 로그인 public PrincipalDetails(User user, Map attributes) { this.user = user; this.attributes = attributes; } @Override public Map getAttributes() { return attributes; } @Override public String getName() { return String.valueOf(user.getId()); } // 권한 (원래 권한이 여러개 있을 수 있으므로 Collection 루프 돌려야 함) @Override public Collection getAuthorities() { Collection authorities = new ArrayList<>(); // collect.add(new GrantedAuthority() { // @Override // public String getAuthority() { // return memberDto.getMemberRole(); // "ROLE_" + @가 되어야 함 // } // }); //스프링부트 2.5 이상 ROLE 슬러시를 붙이면 안된다. 자동으로 붙여준다. authorities.add( () -> { return user.getRole().toString();}); return authorities; } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } // 계정 만료(false) 여부 @Override public boolean isAccountNonExpired() { return true; } // 계정 정지(false) 여부 @Override public boolean isAccountNonLocked() { return true; } // 계정 신용(false) 여부 @Override public boolean isCredentialsNonExpired() { return true; } // 계정 활성(false) 여부 (예 : 최종 로그인 후 일정기간 경과 여부 -> 휴면계정) @Override public boolean isEnabled() { return true; } }
스프링부트 2.2 이하에서는 다음과 같이 SPRING_SECURITY_CONTEXT 추가했으나 , 할 필요가 없다.
/** 스프링 시큐리티 세션 변경 처리 */ //세션 초기화 SecurityContextHolder.clearContext(); UserDetails updateUserDetails = new PrincipalDetails(memberDto); Authentication newAuthentication = new UsernamePasswordAuthenticationToken(updateUserDetails, null, updateUserDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(newAuthentication); session.setAttribute("SPRING_SECURITY_CONTEXT", newAuthentication);
참조:
http://macaronics.net/index.php/m01/spring/view/1748
소스 : https://github.com/braverokmc79/Springboot-JPA-Blog
1. HttpServletRequest 객체를 사용하는 방법
스프링 시큐리티를 사용할때 HttpServletRequest 객체는 실제로 SecurityContextHolderAwareRequestWrapper 객체의 입니다. 스크링 시큐리티 필터가 원본 HttpServletReuest를 래핑해서 기능을 추가합니다.
Controller에서는 HttpServletRequest 객체를 메소드의 인자로 지정해 주기만 하면 쉽게 얻을 수 있습니다. 다른곳에서 사용하기 위해서는 메소드 호출시에 인자로 넘겨주던가 아니면 ContextLoader 를 사용해서 몇 단계 거쳐서 얻어야 합니다. "스프링 빈(bean) 및 서블릿(servlet) 객체 직접 얻기" 를 참조하세요.
- boolean request.isUserInRole("ROLE_ADMIN") : 인자로 주어진 롤을 가진 사용자이면 true 아니면 false를 반환 합니다.
- Principal request.getUserPrincipal() : 로그인 한 사용자 정보를 가지고 있는 객체를 반환합니다. 기본은 UserDetails 타입의 객체입니다. 전자정부표준프레임웍이라면 EgovUserDetails 타입의 객체가 반환됩니다.
- String request.getRemoteUser() : 사용자 아이디가 반환됩니다. UserDetails객체의 getUsername() 을 호출한 반환값입니다.
2. SecurityCotnext 객체를 사용하는 방법
org.springframework.security.core.context.SecurityContext 객체로부터 원하는 권한 정보를 얻을 수 있습니다.
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; // 시큐리티 컨텍스트 객체를 얻습니다. SecurityContext context = SecurityContextHolder.getContext(); // 인증 객체를 얻습니다. Authentication authentication = context.getAuthentication(); // 로그인한 사용자정보를 가진 객체를 얻습니다. Principal principal = authentication.getPrincipal(); // 사용자가 가진 모든 롤 정보를 얻습니다. Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); Iterator<? extends GrantedAuthority> iter = authorities.iterator(); while (iter.hasNext()) { GrantedAuthority auth = iter.next(); System.out.println(auth.getAuthority()); }
위의 코드를 여기 저기서 계속 사용하려면 원하는 기능을 가진 유틸리티 클래스를 하나 만들어서 사용하는 것이 좋겠습니다. 전자정부표준프레임워크에서는 이러한 기능을 가진 클래스를 제공해 줍니다.
egovframework.rte.fdl.security.userdetails.util.EgovUserDetailsHelper // 로그인 사용자 정보 객체(LoginVO) public static Object getAuthenticatedUser() // 사용자의 롤 리스트를 얻습니다. public static List getAuthorities() // 로그인 여부를 확인합니다. public static Boolean isAuthenticated()
EgovUserDetailsHelper 클래스는 final 클래스로 상속받아서 기능을 추가할 수는 없습니다. 더 많은 기능이 필요하다면 자신이 만들어서 사용해야 겠습니다.
3. 시큐리티 태그 라이브러리를 사용하는 방법
- 스프링 시큐리티 태그 라이브러리를 사용하기 위한 메이븐 의존성을 추가합니다.
전자정부표준프레임워크를 사용하고 있다면, egovframework.rte.fdl.security의 의존 라이브러리로 포함되어 있습니다.
<dependency> <groupId>egovframework.rte</groupId> <artifactId>egovframework.rte.fdl.security</artifactId> <version>3.6.0</version> <scope>compile</scope> </dependency>
그렇지 않다면 다음을 직접 pom.xml 파일에 추가한다.
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>3.2.4.RELEASE</version> <scope>compile</scope> </dependency>
- jsp 페이지에 태그 정의를 포함합니다.
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
- 권한을 체크하는 태그 입니다.
* 로그인 되지 않았다면 참입니다.
<sec:authorize access="isAnonymous()"></sec:authorize>
* 로그인 했다면 참입니다.
<sec:authorize access="isAuthenticated()"></sec:authorize>
* 인자로 주어진 롤을 가지고 있다면 참입니다.
<sec:authorize access="hasRole('ROLE_ADMIN')"></sec:authorize>
* 인자로 주어진 롤을 가지고 있지 않다면 참입니다.
<sec:authorize access="!hasRole('ROLE_ADMIN')"></sec:authorize>
* 인자로 주어진 롤들중 하나라도 가지고 있다면 참입니다.
<sec:authorize access="hasAnyRole('ROLE_ADMIN','ROLE_MANAGER')"></sec:authorize>
만약 시큐리티 태그를 사용하고자 하는데 다음과 같은 에러가 발생할 수 있습니다.
javax.servlet.ServletException: javax.servlet.jsp.JspException: java.io.IOException: No visible WebSecurityExpressionHandler instance could be found in the application context. There must be at least one in order to support expressions in JSP 'authorize' tags.
이 경우 스크링 시큐리티 설정에 WebSecurityExpressionHandler 빈을 설정하면 됩니다.
<bean id="webexpressionHandler" class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler" />
또 다른 방법은 프링 시큐리티 설정의 <http> 요소에 user-expressions="true"를 사용하면 됩니다.
<http user-expressions="true">
이외에도 다양한 태그와 서비스 메소드에 아노테이션을 이용하여 권한을 제어할 수 있는 방법등이 있습니다.
더 자세한 사항은 스프링 시큐리티 매뉴얼을 참조하세요.
https://docs.spring.io/autorepo/docs/spring-security/
출처: https://offbyone.tistory.com/217 [쉬고 싶은 개발자]
댓글 ( 4)
댓글 남기기