인증을 위한 사용자 테이블 생성
테이블명은 무조건 USERS로 지정해야 한다.
CREATE TABLE users(username varchar2(100) primary key, password varchar2(100), enabled varchar2(1));
삽입
INSERT INTO users VALUES ('abc123', '123456', '1');
INSERT INTO users VALUES ('xyz789', '123456', '1');
INSERT INTO users VALUES ('opq456', '123456', '1');
INSERT INTO users VALUES ('test', '123456', '0');
AUTHORITIES 테이블 및 인덱스 생성
CREATE TABLE authorities(username VARCHAR2(100), authority VARCHAR2(50)
, CONSTRAINT auth_username_fk FOREIGN KEY(username) REFERENCES users(username));
CREATE UNIQUE INDEX auth_username_index ON authorities(username, authority);
권한부여
INSERT INTO authorities VALUES ('abc123', 'ROLE_USER');
INSERT INTO authorities VALUES ('abc123', 'ROLE_MANAGER');
INSERT INTO authorities VALUES ('xyz789', 'ROLE_MANAGER');
INSERT INTO authorities VALUES ('opq456', 'ROLE_ADMIN');
INSERT INTO authorities VALUES ('test', 'ROLE_USER');
security-context.xml
jdbc-user-service : Spring Security가 JDBC를 이용하는 인증 처리를 하는 경우 사용하는 경우
=> JdbcUserDetailsManager 클래스를 사용하여 인증과 인가 처리한다.
=> USERS 테이블을 생성하여 사용자 정보를 저장하고 AUTHORITIES 테이블을 생성하여 사용자 권한 정보를 저장할 경우
SQL 명령을 제공하지 않아도 기본적으로 제공되는 SQL 명령으로 인증 처리 후 사용자 정보(UserDetils 객체)를 반환한다.
data-source-ref 속성 : Connection 객체를 제공하기 위한 DataSource 관련 클래스에 대한 Spring Bean의 식별자(beanName)을 속성값으로 설정
<authentication-manager>
<authentication-provider>
<jdbc-user-service data-source-ref="dataSource"/>
</authentication-provider>
</authentication-manager>
root-context.xml에 있는 dataSource를 가져다 사용한다.
CustomPasswordEncoder
//비밀번호를 전달받아 암호화 처리하여 반환하거나 암호화 처리된 비밀번호를 비교한 결과를 반환
//하는 메소드를 제공하는 클래스
// => PasswordEncoder 인터페이스를 상속받아 작성한다.
public class CustomPasswordEncoder implements PasswordEncoder {
//매개변수로 전달받은 문자열을 암호화 처리하여 반환하는 메소드
@Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString();
}
//매개변수로 전달받은 암호화된 문자열과 일반 문자열을 비교하여 결과를 논리값으로 반환하는 메소드
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return rawPassword.toString().equals("encodedPassword");//암호화 처리되어있지 않은 비밀번호 비교
}
}
security-context.xml
password-encoder : 암호화 처리된 비밀번호를 비교하기 위한 기능을 제공하는 엘리먼트
ref 속성 : PasswordEncoder 인터페이스를 상속받은 클래스에 대한 Spring Bean의 식별자를 속성값으로 설정
<authentication-manager>
<authentication-provider>
...
<password-encoder ref="customPasswordEncoder"/>
</authentication-provider>
</authentication-manager>
<!-- Spring Security 관련 클래스를 Spring Bean으로 등록 -->
...
<beans:bean class="xyz.itwill.security.CustomPasswordEncoder" id="customPasswordEncoder"/>
테이블 생성
CREATE TABLE security_users(userid VARCHAR2(100) PRIMARY KEY, passwd VARCHAR2(100)
, name VARCHAR2(50), email VARCHAR2(100), enabled VARCHAR2(1));
CREATE TABLE security_auth(userid VARCHAR2(100), auth VARCHAR2(50)
, CONSTRAINT auth_userid_fk FOREIGN KEY(userid) REFERENCES security_users(userid));
CREATE UNIQUE INDEX auth_userid_index ON security_auth(userid, auth);
DTO 생성
@Data
@NoArgsConstructor
@AllArgsConstructor //객체를 생성할 때 입력받아 저장할 수 있도록 하기 위해 선언
public class SecurityUsers {
private String userid;
private String passwd;
private String name;
private String email;
private String enabled;
private List<SecurityAuth> securityAuthList; //조인할때 사용
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SecurityAuth {
private String userid;
private String auth;
}
생성자는 안만들어도 됨
Mppaer
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xyz.itwill.mapper.SecurityUsersMapper">
<insert id="insertSecurityUsers">
insert into security_users values(#{userid}, #{passwd}, #{name}, #{email}, #{enabled})
</insert>
<insert id="insertSecurityAuth">
insert into security_auth values(#{userid}, #{auth})
</insert>
<resultMap type="SecurityUsers" id="securityUsersResultMap">
<id column="userid" property="userid"/>
<result column="passwd" property="passwd"/>
<result column="name" property="name"/>
<result column="email" property="email"/>
<result column="enabled" property="enabled"/>
<collection property="securityAuthList" ofType="SecurityAuth">
<result column="userid" property="userid"/>
<result column="auth" property="auth"/>
</collection>
</resultMap>
<select id="selectSecurityUsersByUserid" resultMap="securityUsersResultMap">
select users.userid, passwd, name, email, enabled, auth from security_users users
left join security_auth auth on users.userid=auth.userid where users.userid=#{userid}
</select>
</mapper>
interface
public interface SecurityUsersMapper {
int insertSecurityUsers(SecurityUsers users);
int insertSecurityAuth(SecurityAuth auth);
SecurityUsers selectSecurityUsersByUserid(String userid);
}
DAO
SecurityUsersRepository
SecurityUsersRepositoryImpl
public interface SecurityUsersRepository {
int insertSecurityUsers(SecurityUsers users);
int insertSecurityAuth(SecurityAuth auth);
SecurityUsers selectSecurityUsersByUserid(String userid);
}
@Repository
@RequiredArgsConstructor
public class SecurityUsersRepositoryImpl implements SecurityUsersRepository {
private final SqlSession sqlSession;
@Override
public int insertSecurityUsers(SecurityUsers users) {
return sqlSession.getMapper(SecurityUsersMapper.class).insertSecurityUsers(users);
}
@Override
public int insertSecurityAuth(SecurityAuth auth) {
return sqlSession.getMapper(SecurityUsersMapper.class).insertSecurityAuth(auth);
}
@Override
public SecurityUsers selectSecurityUsersByUserid(String userid) {
return sqlSession.getMapper(SecurityUsersMapper.class).selectSecurityUsersByUserid(userid);
}
}
Service
//인터페이스
public interface SecurityUsersService {
void addSecurityUsers(SecurityUsers users);
void addSecurityAuth(SecurityAuth auth);
SecurityUsers getSecurityUsers(String userid);
@Service
@RequiredArgsConstructor
public class SecurityUsersServiceImpl implements SecurityUsersService {
private final SecurityUsersRepository securityUsersRepository;
@Override
public void addSecurityUsers(SecurityUsers users) {
securityUsersRepository.insertSecurityUsers(users);
}
@Override
public void addSecurityAuth(SecurityAuth auth) {
securityUsersRepository.insertSecurityAuth(auth);
}
@Override
public SecurityUsers getSecurityUsers(String userid) {
return securityUsersRepository.selectSecurityUsersByUserid(userid);
}
}
컨트롤러
@Controller
@RequiredArgsConstructor
public class SecurityUsersController {
private final SecurityUsersService securityUsersService;
private final PasswordEncoder passwordEncoder;
@RequestMapping(value = "/security/add", method = RequestMethod.GET)
@ResponseBody //뷰 만들지 않고 처리하기 위함
public String addUsers() {
SecurityUsers user1=new SecurityUsers("abc123",
passwordEncoder.encode("123456"), "홍길동", "abc@itwill.xyz", "1", null);
SecurityUsers user2=new SecurityUsers("xyz789",
passwordEncoder.encode("123456"), "임꺽정", "xyz@itwill.xyz", "1", null);
SecurityUsers user3=new SecurityUsers("opq456",
passwordEncoder.encode("123456"), "전우치", "opq@itwill.xyz", "1", null);
securityUsersService.addSecurityUsers(user1);
securityUsersService.addSecurityUsers(user2);
securityUsersService.addSecurityUsers(user3);
SecurityAuth auth1=new SecurityAuth("abc123", "ROLE_USER");
SecurityAuth auth2=new SecurityAuth("abc123", "ROLE_MANAGER");
SecurityAuth auth3=new SecurityAuth("xyz789", "ROLE_MANAGER");
SecurityAuth auth4=new SecurityAuth("opq456", "ROLE_ADMIN");
securityUsersService.addSecurityAuth(auth1);
securityUsersService.addSecurityAuth(auth2);
securityUsersService.addSecurityAuth(auth3);
securityUsersService.addSecurityAuth(auth4);
return "사용자를 성공적으로 등록 하였습니다.";
}
}
환경설정
passwordEncoder를 스프링 빈으로 등록
security-context.xml
<beans:bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="passwordEncoder"/>
BCryptPasswordEncoder : 문자열을 암호화 처리하거나 비교 결과를 제공하기 위한 클래스
servlet-context.xml
<context:component-scan base-package="xyz.itwill.auth" />
<context:component-scan base-package="xyz.itwill.repository" />
<context:component-scan base-package="xyz.itwill.service" />
http://localhost:8000/auth/security/add 경로를 입력하면


테이블에 정보가 삽입된 것을 확인할 수 있다.
security-context.xml
password-encoder ref를 passwordEncoder로 바꿔준다 (암호화)
<authentication-manager>
<authentication-provider>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="select userid, passwd, enabled from security_users where userid=?"
authorities-by-username-query="select userid, auth from security_auth where userid=?"/>
<password-encoder ref="passwordEncoder"/>
</authentication-provider>
</authentication-manager>
users-by-username-query : 아이디를 전달받아 아이디(userid), 비밀번호(passwd), 활성화 상태(enabled) 를 검색하는 SELECT 명령을 속성값으로 설정 - 인증처리
authorities-by-username-query : 아이디를 전달받아 아이디, 권한을 검색하는 SELECT 명령을 속성값으로 설정한다. - 인증 성공한 사용자에게 권한 제공
abc123 계정으로 로그인 해보자

권한을 확인할 수 있다. (ROLE_MANAGER, ROLE_USER)
이처럼 직접 만든 테이블을 이용해 인증과 인가를 제공받을 수 있다.
User 클래스를 사용하지 않고 UserDetails 인터페이스를 상속받는 클래스를 선언해서 인증된 사용자 정보를 저장해보자
(User 클래스를 상속받아 작성하는 것도 가능하다.)
CustomUserDetails
//인증된 사용자 정보를 저장하기 위한 클래스
// => UserDetails 인터페이스를 상속받아 작성
// => UserDetails 인터페이스 대신 UserDetails 인터페이스를 상속받은 User 클래스를 상속받아 작성하는 것도 가능하다.
@Data
public class CustomUserDetails implements UserDetails {
private static final long serialVersionUID = 1L;
//인증된 사용자 정보가 저장될 필드 선언 (필드명은 자유롭게 작성 가능) - 세션에 저장될 정보
private String userid;
private String passwd;
private String name;
private String email;
private String enabled;
//인증된 사용자의 권한 정보가 저장될 필드 선언
private List<GrantedAuthority> securityAuthList;
//매개변수로 전달받은 SecurityUsers 객체의 필드값을 CustomUserDetails 클래스의 필드에 저장
public CustomUserDetails(SecurityUsers users) {
this.userid=users.getUserid();
this.passwd=users.getPasswd();
this.name=users.getName();
this.email=users.getEmail();
this.enabled=users.getEnabled();
//검색된 사용자의 권한(String 객체)은 GrantedAuthority 객체로 생성하여 List 객체의 요소로 저장
this.securityAuthList=new ArrayList<GrantedAuthority>();
for(SecurityAuth auth : users.getSecurityAuthList()) {
securityAuthList.add(new SimpleGrantedAuthority(auth.getAuth()));
}
}
//인증 사용자의 권한 정보를 반환하는 메소드
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return securityAuthList;
}
//인증 사용자의 비밀번호를 반환하는 메소드
@Override
public String getPassword() {
return passwd;
}
//인증 사용자의 식별자(아이디)를 반환하는 메소드
@Override
public String getUsername() {
return userid;
}
//인증 사용자의 계정 만료 관련 상태 정보를 반환하는 메소드
// => false : 계정 사용 유효기관 초과 상태, true : 계정의 사용 유효기간 미만 상태
@Override
public boolean isAccountNonExpired() {
return true;
}
//인증 사용자의 계정 잠금 상태 정보를 반환하는 메소드
// => false : 계정 잠금 상태, true : 계정 해제 상태
@Override
public boolean isAccountNonLocked() {
return true;
}
//인증 사용자의 비밀번호 유효기간 상태 정보를 반환하는 메소드
// => false : 비밀번호 유효기간 초과 상태, true : 비밀번호 유효기간 미만 상태
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//인증 사용자의 활성화 상태 정보를 반환하는 메소드
// => false : 사용자 비활성화 상태, true : 사용자 활성화 상태
@Override
public boolean isEnabled() {
if(enabled.equals("0")) {
return false;
} else {
return true;
}
}
}
Spirng Security 필터는 root-context.xml 또는 security-context.xml에 권한 설정 해주어야 한다.
security-context.xml
<context:component-scan base-package="xyz.itwill.security"/>
<context:component-scan base-package="xyz.itwill.repository" />
CustomUserDetailsService
//인증 처리 후 인증된 사용자 정보와 권한을 저장한 UserDetails 객체로 반환하는 기능을 제공하는 클래스
// => UserDetailsService 인터페이스를 상속받아 작성
@Service
@RequiredArgsConstructor //DAO를 가져다 쓰기 위함
public class CustomUserDetailsService implements UserDetailsService {
private final SecurityUsersRepository securityUsersRepository;
//매개변수로 아이디(username)를 전달받아 DB에 저장된 사용자 정보를 검색하여 검색된 사용자 정보를
//UserDetails 객체를 생성하여 반환하는 메소드
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SecurityUsers users=securityUsersRepository.selectSecurityUsersByUserid(username);
if(users==null) {
throw new UsernameNotFoundException(username);
}
return new CustomUserDetails(users);//사용자 정보가 검색된 경우 UserDetails 객체로 반환
}
}
security-context.xml
user-service-ref 속성 : 인증에 필요한 사용자 및 권한 정보(UserDetails 객체)가 저장된 객체를 반환하는 클래스(UserDetailsService)에 대한 Spring Bean의 식별자(beanName)을 속성값으로 설정
<authentication-manager>
<authentication-provider user-service-ref="customUserDetailsService">
<password-encoder ref="passwordEncoder"/>
</authentication-provider>
</authentication-manager>
abc123으로 로그인

'학원 > 복기' 카테고리의 다른 글
[Spring Security] rememberme 예제 (0) | 2023.09.11 |
---|---|
[Spring] Spring Security 로그인 커스터마이징 (0) | 2023.09.07 |
[Spring] Spring Security (0) | 2023.09.06 |
[Spring] 스프링 검증(Spring Validation) (0) | 2023.09.04 |
[Spring] @RestController - 수정 (0) | 2023.08.19 |