권한은 user,MANAGER,ADMIN 으로 나누어지지만 이 모두는 Member 엔티티에 들어감 *USER*, *MANAGER*, *ADMIN*}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private static final String[] AUTH_WHITElIST = {
"/", "/sample/all"
};
@Bean
protected SecurityFilterChain config(HttpSecurity httpSecurity)
throws Exception {
// authorizeHttpRequests :: 선별적으로 접속을 제한하는 메서드
// 모든 페이지가 인증을 받도록 되어 있는 상태
// httpSecurity.authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
httpSecurity.authorizeHttpRequests(
auth -> auth.requestMatchers(AUTH_WHITElIST).permitAll()
.requestMatchers("/sample/admin/**").hasRole("ADMIN")
.requestMatchers("/sample/member/**").access(
new WebExpressionAuthorizationManager(
"hasRole('ADMIN') or hasRole('MEMBER')")
)
.anyRequest().authenticated());
// formLogin()를 정의해야만 해도 자동생성된 로그인페이지로 이동가능.
httpSecurity.formLogin(new Customizer<FormLoginConfigurer<HttpSecurity>>() {
@Override
public void customize(FormLoginConfigurer<HttpSecurity> httpSecurityFormLoginConfigurer) {
httpSecurityFormLoginConfigurer
.loginPage("/sample/login")
.loginProcessingUrl("/sample/login")
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
}
})
.defaultSuccessUrl("/");
}
});
// logout() 정의 안해도 로그아웃 페이지 사용 가능. 사용자 로그아웃 페이지 지정할 때사용
httpSecurity.logout(new Customizer<LogoutConfigurer<HttpSecurity>>() {
@Override
public void customize(LogoutConfigurer<HttpSecurity> httpSecurityLogoutConfigurer) {
httpSecurityLogoutConfigurer
// logoutUrl() 설정할 경우 html action 주소 또한 같이 적용해야 함.
// logoutUrl()으로 인해 기존 logout 주소 이동은 가능하나 기능은 사용 안됨.
.logoutUrl("/sample/logout")
.logoutSuccessUrl("/") // 로그아웃 후에 돌아갈 페이지 설정
.logoutSuccessHandler((request, response, authentication) -> {
// logout 후에 개별적으로 여러가지 상황에 대하여 적용 가능한 설정
})
.invalidateHttpSession(true); // 서버 세션을 무효화, false도 클라이언트측 무효화
}
});
return httpSecurity.build();
}
/* SecurityFilterChain Bean 역활 : 세션 인증 기반 방식으로 대부분의 Spring Security에 대한 설정으로 다룰수 있다. */
// InMemory 방식으로 계정의 권한 관리
@Bean
public UserDetailsService userDetailsService() {
UserDetails user1 = User.builder()
.username("user1")
.password("$2a$10$XGw3jOo9mQSoij4/so.6H.BtSRWpgPze6ZWMuc7ntyFFWqVNbcmBe")
.roles("USER")
.build();
UserDetails member = User.builder()
.username("member")
.password("$2a$10$AEHcuzENZx7OLeA.s8e.t.CvhE/a/GZf.ZKTPEBIKLv8g03zChnD2")
.roles("MEMBER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder().encode("1"))
.roles("ADMIN","MEMBER")
.build();
List<UserDetails> list = new ArrayList<>();
list.add(user1);list.add(member);list.add(admin);
return new InMemoryUserDetailsManager(list);
}
로그인 기능
// formLogin()를 정의만 해도 자동생성된 로그인페이지로 이동가능.
httpSecurity.formLogin(new Customizer<FormLoginConfigurer<HttpSecurity>>() {
@Override
public void customize(FormLoginConfigurer<HttpSecurity> httpSecurityFormLoginConfigurer) {
}
});
로그아웃 기능
//logout()은 정의 안해도 로그아웃 페이지 설정 가능. 사용자 로그아웃 페이지 지정할 때 사용
httpSecurity.logout(new Customizer<LogoutConfigurer<HttpSecurity>>() {
@Override
public void customize(LogoutConfigurer<HttpSecurity> httpSecurityLogoutConfigurer) {
httpSecurityLogoutConfigurer.logoutUrl("/sample/logout")
.logoutSuccessUrl("/index")
.invalidateHttpSession(true);
}
});
@SpringBootTest
class ClubMemberRepositoryTests {
@Autowired
private ClubMemberRepository clubMemberRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Test
public void insertDummies(){
IntStream.rangeClosed(1,100).forEach(i -> {
ClubMember clubMember = ClubMember.builder()
.email("user"+i+"@a.a")
.name("사용자"+i)
.fromSocial(false) //구글 로그인 처리
.password(passwordEncoder.encode("1")) //비밀번호 1로 통일
.build();
clubMember.addMemberRole(ClubMemberRole.USER);
if(i>80){clubMember.addMemberRole(ClubMemberRole.MANAGER);}
if(i>90){clubMember.addMemberRole(ClubMemberRole.ADMIN);}
clubMemberRepository.save(clubMember);
});
}
}
public interface ClubMemberRepository extends JpaRepository<ClubMember, String> {
//attributePaths 에 정의된 속성은 eager(즉각적)로 패치하고, 나머지는 lazy 패치
@EntityGraph(attributePaths = {"roleSet"}, type = EntityGraph.EntityGraphType.LOAD)
@Query("select m from ClubMember m where m.email=:email and m.fromSocial=:social")
Optional<ClubMember> findByEmail(String email , boolean social);
}
@Test
public void testRead(){
Optional<ClubMember>result = clubMemberRepository.findByEmail("user100@a.a",false);
if(result.isPresent()) System.out.println(result.get());
}
로그인 정보에 대한 정보를 전달하기 위한 DTO
@Log4j2
@Getter
@Setter
public class ClubMemberAuthDTO extends User {
private Long cno;
private String email;
private boolean fromSocial;
public ClubMemberAuthDTO(Long cno, String username, String password,
String email, boolean fromSocial,
Collection<? extends GrantedAuthority> authorities)
{
super(username, password, authorities);
this.cno = cno;
this.email = email;
this.fromSocial = fromSocial;
}
}
@Log4j2
@Service
public class ClubUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("ClubMemberUser......",username);
return null;
}
}
@Log4j2
@Service
@RequiredArgsConstructor
public class ClubUserDetailsService implements UserDetailsService {
private final ClubMemberRepository clubMemberRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("ClubMemberUser.........", username);
Optional<ClubMember> result = clubMemberRepository.findByEmail(username, false);
if (!result.isPresent()) throw new UsernameNotFoundException("Check Email or Social");
ClubMember clubMember = result.get();
//String username, String password,
// Long cno, boolean fromSocial,
// Collection<? extends GrantedAuthority> authorities
ClubMemberAuthDTO clubMemberAuthDTO = new ClubMemberAuthDTO(
clubMember.getEmail(), clubMember.getPassword(), clubMember.getCno(),
clubMember.isFromSocial(),
clubMember.getRoleSet().stream().map(
clubMemberRole -> new SimpleGrantedAuthority(
"ROLE_" + clubMemberRole.name())).collect(Collectors.toList())
);
clubMemberAuthDTO.setName(clubMember.getName());
clubMemberAuthDTO.setFromSocial(clubMember.isFromSocial());
log.info("clubMemberAuthDTO >> ", clubMemberAuthDTO.getCno());
return clubMemberAuthDTO;
}
}
Spring Security 가 자동적으로 login 페이지를 생성해주고 관리해줌
Spring Security : 사용자의 UserName 이 중요하다
이름을 가지고 SpringSecurity 에서 내부적으로 처리를 함
Security Context = Login SuccesHandler
//불편해서 사용하지 않음
*UserDetailsSevrvice ⇒ InMemory방식으로 계저의 권한을 관리함
→loadUserName UserDetails 안에 불러온 데이터의 값을 저장해줌
@Bean
public UserDetailsService userDetailsService() {
UserDetails user1 = User.builder()
.username("user1")
.password("$2a$10$XGw3jOo9mQSoij4/so.6H.BtSRWpgPze6ZWMuc7ntyFFWqVNbcmBe")
.roles("USER")
.build();
UserDetails member = User.builder()
.username("member")
.password("$2a$10$AEHcuzENZx7OLeA.s8e.t.CvhE/a/GZf.ZKTPEBIKLv8g03zChnD2")
.roles("MEMBER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder().encode("1"))
.roles("ADMIN", "MEMBER")
.build();
List<UserDetails> list = new ArrayList<>();
list.add(user1);
list.add(member);
list.add(admin);
return new InMemoryUserDetailsManager(list);
}
userDetailsService 의 역할: DB 접근 방식으로UserDetailsService(인증 관리 객체) 사용
Authentication(in Memory 방식)
@Service @RequireArgsConstructor
clubMemberRepository.findByEmail(username);
에서 이름을 찾음
이름이 있으면 DB에서 해당하는 user의 이름을 가져와서 pw에 작성된 암호화된 비번과 비교를 해서 해당하는 정보를 가져옴
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Log4j2
@Service
@RequiredArgsConstructor //DB접근방식으로 UserDetailsService(인증 관리 객체) 사용
public class ClubUserDetailsService implements UserDetailsService {
private final ClubMemberRepository clubMemberRepository;
@Override
// DB에 있는 것 확인 된후,User를 상속받은 ClubMemberAuthDTO에 로그인정보를 담음=>세션
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("ClubMemberUser.........", username);
Optional<ClubMember> result = clubMemberRepository.findByEmail(username);
if (!result.isPresent()) throw new UsernameNotFoundException("Check Email or Social");
if (!result.isPresent()) throw new UsernameNotFoundException("Check Email or Social");
ClubMember clubMember = result.get(); //DB로 부터 검색한, Entity
// 엔티티를 세션으로 담기위해 만든 ClubMemberAuthDTO
ClubMemberAuthDTO clubMemberAuthDTO = new ClubMemberAuthDTO(
clubMember.getEmail(), clubMember.getPassword(), clubMember.getCno(),
clubMember.isFromSocial(),
clubMember.getRoleSet().stream().map(
clubMemberRole -> new SimpleGrantedAuthority(
"ROLE_" + clubMemberRole.name())).collect(Collectors.toList())
);
clubMemberAuthDTO.setName(clubMember.getName());
clubMemberAuthDTO.setFromSocial(clubMember.isFromSocial());
log.info("clubMemberAuthDTO >> ", clubMemberAuthDTO.getCno());
return clubMemberAuthDTO;
}
}
public UserDetails loadUserByUsername(String username)
이부분은 Spring Security 에서 제공해줌
비밀번호를 따로 비교하지 않아도 됨, 여기서 알아서 해줌
전송 될 때 name으로 값을 넘겨줌Optional<ClubMember> result = clubMemberRepository.findByEmail(username, false);
ClubMember에 Email 이 존재하면 ClubMember clubMember = result.get();
에 결과를 담아줌
DB에 있는 것이 확인 되고 , User를 상속 받은 ClubMemberAuthDTO에 로그인 정보를 담음 ⇒ 세션
@Log4j2
@Getter
@Setter
@ToString
public class ClubMemberAuthDTO extends User {
private Long cno;
private String email;
private String name;
private boolean fromSocial;
public ClubMemberAuthDTO(String username, String password,
Long cno, boolean fromSocial,
Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
this.cno = cno;
this.email = username; //★UserDetails에서 username은 email로 기준하기 때문.
this.fromSocial = fromSocial;
}
}
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.tymeleaf.orf/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>For Manager</h1>
<div sec:authrize ="hasRole('USER')" >Has USER_ROLE</div>
<div sec:authrize ="hasRole('MANAGER')" >Has USER_MANAGER</div>
<div sec:authrize ="hasRole('ADMIN')" >Has USER_ADMIN</div><br>
<div sec:authrize ="isAuthenicated('ADMIN')" >
<!-- 로그인했을 때만 보여짐-->
Authenticated memer can just see this page.
</div>
<div>
아래의 태그들은 로그인 되었을 땜나 사용가능.<br>
Authenticated username :<span sec:authentication ="name"></span><br>
Authenticated user role :<span sec:authentication ="principal.authorities"></span><br>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>For ALL</h1>
<div sec:authrize ="hasRole('USER')" >Has USER_ROLE</div>
<div sec:authrize ="hasRole('MANAGER')" >Has USER_MANAGER</div>
<div sec:authrize ="hasRole('ADMIN')" >Has USER_ADMIN</div><br>
<fieldset sec:authrize ="isAuthenicated()" >
<!-- 로그인했을 때만 보여짐-->
<legend>
Authenticated memer can just see this page.
</legend>
<div>
아래의 태그들은 로그인 되었을 때만 사용가능.<br>
만약 fieldset 태그의 isAuthenticated 가 없이 사용한다면 에러발생.<br>
에러와 함께 로그인하는 페이지로 연결됨 <br>
Authenticated username :<span sec:authentication ="name"></span><br>
Authenticated user role :<span sec:authentication ="principal.authorities"></span><br>
</div>
</fieldset>
Authenticated username :<span sec:authentication ="name"></span><br>
Authenticated user role :<span sec:authentication ="principal.authorities"></span><br>
</body>
</html>
'PROGRAMING📚 > Spring📑' 카테고리의 다른 글
JSP(JavaServer Pages) (1) | 2024.09.25 |
---|---|
Spring : 구글 로그인 세션 만들기 (1) | 2024.09.25 |
SecuritConfig -코드 해석 (0) | 2024.09.25 |
[Spring]::Startbootstrap - Simple Sidebar 사용하기 (0) | 2024.07.11 |
[Spring]::Fragments (0) | 2024.07.11 |
댓글