2024. 11. 28. 13:31ㆍJAVA/Spring Security
환경설정이 끝났으니 본격적으로 Security 설정을 시작하도록 한다.
1. Spring Security Config
Security 및 Annotation에 대해서는 별도 설명을 하지 않는다.
package kr.co.infob.config.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import lombok.RequiredArgsConstructor;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
@Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
//Login, Logout 등 기본 방식 미사용 처리
.formLogin(AbstractHttpConfigurer::disable)
.logout(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
//JWT를 사용할 예정으로 Session 사용하지 않음
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
//요청 URL 검증 추리
.authorizeHttpRequests(requests ->
requests
.anyRequest()
.permitAll()
)
;
return http.build();
}
}
우선 기본 설정만한 상태이다.
JWT를 사용하기 때문에 로그인, 로그아웃 관련해서는 사용하지 않는 것으로 하였고
또한 JWT 사용으로 Session 사용을 하지 않는 것으로 설정하였다.
테스트를 위해 모든 요청에 대해서 권한 체크를 하지 않도록 설정하였다.
테스트 Controller를 생성한다.
package kr.co.infob.api.auth.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/auth")
public class AuthController {
@GetMapping("/signup")
public ResponseEntity<?> signup() {
return ResponseEntity.ok("Sign UP!!!!!!");
}
}
위와 같이 Controller을 생성하고 테스트를 진행한다.
테스트는 Postman을 사용하여 진행한다.
위와 같이 찍히면 우선 1차적으로 성공했다.
이제부터 Security를 하나씩 체워 나가기로 한다.
2. Entity 및 Vo생성
나의 경우에는 사용자 정보 및 권한 정보 등을 관리하는 entityVo를 생성하여 사용한다.
UserDetailsVo : UserDetails Interface를 구현한 Class 사용자에 대한 정보를 담는 Class
Grant : GrantedAuthority를 구현한 Class로 권한 정보를 담는
UrlRoleMapping : Database에서 URL에 따른 권한을 조회하기 위한 Class
common.database.entity 안에 있는 class는 Database 테이블과 1:1 맵핑되는 Class로 구성하였다.
(이건 내가 쓰는 개취...)
RoleInfo.java
package kr.co.infob.common.database.entity;
import java.util.Date;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class RoleInfo {
private String roleCd;
private String roleNm;
private String useYn;
private String registerId;
private Date registDttm;
private String updusrId;
private Date updtDttm;
}
UserInfo.java
package kr.co.infob.common.database.entity;
import java.util.Date;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
@Getter @Setter
@SuperBuilder
@NoArgsConstructor
public class UserInfo {
private String userId;
private String passwd;
private String userNm;
private String cttpc;
private String email;
private Date registDttm;
private String updusrId;
private Date updtDttm;
}
Grant.java
package kr.co.infob.config.security.vo;
import org.springframework.security.core.GrantedAuthority;
import kr.co.infob.common.database.entity.RoleInfo;
@SuppressWarnings("serial")
public class Grant extends RoleInfo implements GrantedAuthority {
@Override
public String getAuthority() {
return getRoleCd();
}
}
UrlRoleMapping.java
package kr.co.infob.config.security.vo;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class UrlRoleMapping {
private String resrcUrl;
private String roleCd;
}
UserDetailsVo.java
package kr.co.infob.config.security.vo;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import kr.co.infob.common.database.entity.UserInfo;
import lombok.Getter;
import lombok.experimental.SuperBuilder;
@SuppressWarnings("serial")
@Getter
@SuperBuilder
public class UserDetailsVo extends UserInfo implements UserDetails {
private Set<GrantedAuthority> authorities;
private List<Grant> roles;
@Override
public String getUsername() {
return getUserId();
}
@Override
public String getPassword() {
return getPasswd();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if(authorities == null){
authorities = new HashSet<GrantedAuthority>();
for(Grant grant : getRoles()){
authorities.add(grant);
}
}
return authorities;
}
/**
* 계정이 만료되지 않았는지 여부를 반환합니다.
* 현재 항상 false를 반환하므로, 모든 계정이 만료된 것으로 처리됩니다.
*
* @return boolean
*/
@Override
public boolean isAccountNonExpired() {
return false;
}
/**
* 계정이 잠기지 않았는지 여부를 반환합니다.
*
* @return boolean
*/
@Override
public boolean isAccountNonLocked() {
return false;
}
/**
* 자격 증명(비밀번호)이 만료되지 않았는지 여부를 반환합니다.
*
* @return boolean
*/
@Override
public boolean isCredentialsNonExpired() {
return false;
}
/**
* 계정이 활성화되어 있는지 여부를 반환합니다.
*
* @return boolean
*/
@Override
public boolean isEnabled() {
return false;
}
}
3. SecurityService
Security에서 사용할 사용자 정보 및 Database에서 URL과 권한을 조회 하도록 한다.
package kr.co.infob.config.security.service;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import kr.co.infob.config.security.SecurityMapper;
import kr.co.infob.config.security.vo.UrlRoleMapping;
import kr.co.infob.config.security.vo.UserDetailsVo;
@Service
public class SecurityService {
@Autowired
private SecurityMapper securityMapper;
/**
* 사용자 정보를 조회 하여 UserDetails에 담는다.
* @param username
* @return
*/
public UserDetailsVo loadUserByUsername(String username) {
UserDetailsVo user = securityMapper.getUsername(username);
if( ObjectUtils.isEmpty(user)) {
throw new UsernameNotFoundException("User not found.");
}
Set<GrantedAuthority> authorities = user.getRoles()
.stream()
.map((role) -> new SimpleGrantedAuthority(role.getRoleCd()))
.collect(Collectors.toSet());
return UserDetailsVo.builder()
.userId(user.getUsername())
.passwd(user.getPasswd())
.authorities(authorities)
.build();
}
/**
* Database에서 URL에 따른 권한 정보를 호출하여 접근 여부를 확인하는데 사용한다.
* @return
*/
public LinkedHashMap<RequestMatcher, List<ConfigAttribute>> selectUrlRoleMapping() {
LinkedHashMap<RequestMatcher, List<ConfigAttribute>> resourcesMap = new LinkedHashMap<RequestMatcher, List<ConfigAttribute>>();
List<UrlRoleMapping> resultList = securityMapper.selectUrlRoleMapping();
String preResource = null;
RequestMatcher presentResource;
List<ConfigAttribute> configList;
for(UrlRoleMapping vo : resultList){
presentResource = new AntPathRequestMatcher(vo.getResrcUrl());
if(preResource != null && vo.getResrcUrl().equals(preResource)){
List<ConfigAttribute> preAuthList = resourcesMap.get(presentResource);
preAuthList.add(new SecurityConfig(vo.getRoleCd()));
}else{
configList = new LinkedList<ConfigAttribute>();
configList.add(new SecurityConfig(vo.getRoleCd()));
resourcesMap.put(presentResource, configList);
}
preResource = vo.getResrcUrl();
}
return resourcesMap;
}
}
4. SecurityMapper 설정
SecurityMapper.java
package kr.co.infob.config.security;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import kr.co.infob.config.security.vo.UrlRoleMapping;
import kr.co.infob.config.security.vo.UserDetailsVo;
@Mapper
public interface SecurityMapper {
/**
* 사용자 정보 조회
* @param username
* @return
*/
UserDetailsVo getUsername(String username);
/**
* URL에 따른 롤정보 조회
* @return
*/
List<UrlRoleMapping> selectUrlRoleMapping();
}
security.xml
Mybatis Query XML 파일
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="kr.co.infob.config.security.mapper.SecurityMapper">
<resultMap type="kr.co.infob.config.security.vo.Grant" id="resultGrant">
<result property="roleCd" column="ROLE_CD"/>
<result property="roleNm" column="ROLE_NM"/>
</resultMap>
<resultMap type="kr.co.infob.config.security.vo.UserDetailsVo" id="resultUserDetailsVo" extends="kr.co.infob.common.database.mapper.UserInfoMapper.resultUserInfo">
<collection property="roles" resultMap="resultGrant" />
</resultMap>
<select id="getUsername" resultMap="resultUserDetailsVo">
/* kr.co.infob.config.security.mapper.SecurityMapper.getUsername */
SELECT
UI.USER_ID,
UI.USER_NM,
UI.CTTPC,
UI.EMAIL,
RI.ROLE_CD,
RI.ROLE_NM
FROM USER_INFO UI
LEFT JOIN USER_ROLE UR ON UI.USER_ID = UR.USER_ID
LEFT JOIN ROLE_INFO RI ON UR.ROLE_CD = RI.ROLE_CD
WHERE
UI.USER_ID = #{username}
</select>
<resultMap type="kr.co.infob.config.security.vo.UrlRoleMapping" id="resultUrlRoleMapping">
<result property="resrcUrl" column="RESRC_URL"></result>
<result property="roleCd" column="ROLE_CD"></result>
</resultMap>
<select id="selectUrlRoleMapping" resultMap="resultUrlRoleMapping">
/* kr.co.infob.config.security.mapper.SecurityMapper.getUsername */
SELECT RESRC.RESRC_URL, ROLE.ROLE_CD, RESRC.RESRC_ORD, 0 ACCESS_RESRC_ORD
FROM RESRC_INFO RESRC
CROSS JOIN ROLE_INFO ROLE
WHERE ACCESS_TYPE_CD = 'COMM'
AND RESRC.RESRC_USE_YN = 'Y'
UNION ALL
SELECT RESRC.RESRC_URL, ROLE.ROLE_CD, RESRC.RESRC_ORD, 0 ACCESS_RESRC_ORD
FROM RESRC_INFO RESRC
CROSS JOIN (
SELECT ROLE_CD FROM ROLE_INFO
UNION ALL
SELECT 'ANONYMOUS' ROLE_CD
) ROLE
WHERE ACCESS_TYPE_CD = 'ALL'
AND RESRC.RESRC_USE_YN = 'Y'
UNION ALL
SELECT ACCESS_RESRC_PTTRN RESRC_URL, ROLE_CD, 1 RESRC_ORD, ACCESS_RESRC_ORD
FROM (
SELECT MI.MENU_SN, ROLE_CD, MI.RESRC_SN
FROM MENU_INFO MI
INNER JOIN MENU_ROLE MR ON MI.MENU_SN = MR.MENU_SN AND MR.READ_AUTH_YN = 'Y'
) MI
INNER JOIN RESRC_ACCESS_AUTH RAA ON MI.RESRC_SN = RAA.RESRC_SN AND RAA.ACCESS_AUTH_SE_CD = 'READ'
UNION ALL
SELECT ACCESS_RESRC_PTTRN RESRC_URL, ROLE_CD, 1 RESRC_ORD, ACCESS_RESRC_ORD
FROM (
SELECT MI.MENU_SN, MR.ROLE_CD, MI.RESRC_SN
FROM MENU_INFO MI
INNER JOIN MENU_ROLE MR ON MI.MENU_SN = MR.MENU_SN AND MR.WRITE_AUTH_YN = 'Y'
) MI
INNER JOIN RESRC_ACCESS_AUTH RAA ON MI.RESRC_SN = RAA.RESRC_SN AND RAA.ACCESS_AUTH_SE_CD = 'WRITE'
UNION ALL
SELECT '/**' RESRC_URL, 'SYSADM' ROLE_CD, 99 RESRC_ORD, 99 ACCESS_RESRC_ORD
ORDER BY RESRC_ORD, ACCESS_RESRC_ORD, RESRC_URL
</select>
</mapper>
참고로 여기서 사용하는 권한은 SYSADM(시스템관리자), USER(사용자)로 한다.
getUsername
별도의 설명은 하지 않겠다.
selectUrlRoleMapping
리스스 정보를 기본으로 리스스에 접근 권한이 있는지와 메뉴에 대한 권한이 있는지 여부등을 기준으로
전체 리소스의 정보를 조회한다.
리소스 정보를 우선 이렇게 입력을 했다.
위와 같이 정보를 입력하고 조회해 보면 바로 이해 될거라 본다.
내용이 길어져서 여기서 끝.
'JAVA > Spring Security' 카테고리의 다른 글
[Spring Security] 권한 접두사(ROLE_) 제거 처리 (0) | 2024.12.06 |
---|---|
[Spring Security] Back-End - Spring Security설정(With JWT) - 3 (0) | 2024.11.28 |
[Spring Security] Back-End - Spring Security설정(With JWT) - 1 (1) | 2024.11.28 |
[Spring Security 6.0] SSO 로그인 시 Anonymous user로 처리 될 때 (0) | 2023.06.27 |
[Spring Security] JSP 파일에서 Custom Tag를 이용한 권한 체크 (0) | 2018.04.25 |