1. spring security는
spring security 를 사용하기 위해서는
<depedency> 를 추가 해줘야한다.
2. spring security 는 filter로 등록해줘야 한다.
3. spring security 설정을 위하여 security-context.xml 을 생성 한 후 contextConfigLocation에 등록해줘야 한다.
>> security-context.xml 에서 크게 2개의 태그가 있는데.
1) <security:http> </security:http>
- <security:intercept-url pattern="" access=""> : 어느 Pattern(URI)에 어떤 Access(권한)을 걸지 설정한다.
즉 Pattern에 적힌 URI로 접속시에 Access에 적시된 권한이 없을경우 Spring security 기본 Login 화면 또는
개인이 설정한 로그인 화면으로 이동된다.
- <security:form-login login-page="" authentication-success-handler-ref="" password-parameter="" 등..>
: login-page 설정을 하지 않으면 Spring security에서 지정한 페이지로 로그인 화면으로 이동되며, login-page를 지
정할 경우 지정한 Login Page로 이동한다.
: authentication-success-handler-ref의 경우는 로그인 성공했을 때 다른 추가적인 액션을 하려면 Class를 생성하여
AuthenticationSuccessHandler를 상속하여 onAuthenticationSuccess() 를 오버라이딩 하여 동작을 지시한다.
>> 지정된 login-page 에서 로그인 실패할 경우 다시 /customLogin 리다이렉트 되며 이때 error 메시지를 가지고
가므로 error가 null이 아니므로 error msg 처리를 할 수 있다.
- <security:access-denied-handler ref=""> : 권한이 없는 아이디로 접근을 시도할 경우 처리하는 동작을 나타내는 클래스 이다. 옵션으로 error-page="" 의 경우 해당 URL로 이동하고 ref=""의 경우 추가적인 작업이 필요한 것으로 Class를 생성한 후 AccessDeniedHandler를 상속하여 처리하도록 한다.
- <security:logout logout-url="" invalidate-session="" success-handler-ref=""> : 로그아웃을 성공했을때 추가적인 동작을 설정하때 suceess-handler-ref 로 지정된 클래스로 이동한다. 이것 또 Class를 생성한 후 LogoutSuccessHandler 클래스를 상속한 후 onLogoutSuccess() 를 오버라이딩 하여 설정해주면된다.
2) <security:authentication-manager>
<security:authentication-provider>
// 이부분의 1,2,3 내용이 달라집니다.
</security:authentication-provider>
</security:authentication-manager>
여기서 설정이 3가지 방식으로 나뉜다.
1. InMemory 방식으로 직접 사용자와 권한을 부여하여 사용하는 방법.
- <security:user name="" password="" authorities=""> : 사용자의 ID와 PWD, Auth를 직접 부여한다.
이때 Password에 {noop}을 붙여 주지 않으면 아래와 같은 에러가 발생한다.
>> spring seuciryt 에서 password는 암호화 과정을 거치게 되는데 이때 암호화 과정인 PasswordEncoder를 거치지 않았기 때문에 아래와 같은 에러가 발생한다 이때 따로 PasswordEncoder를 지정하지 않으려면 {noop}을 지정해준다.
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
2. JDBC 방식으로 기본 TABLE인 users, authorities table을 이용하는 방법
- 만약 users, authorities 테이블이 없을 경우 아래와 같은 에러코드가 발생한다.
- 따라서 JDBC 기본 Table을 통해 연결할때는 users, authorities 테이블이 필수이다.
- users(username(id를 의미) , password, enabled) "enabled"의 의미는 0는 비활성화, 1은 활성화를 나타낸다.
* null 값일 경우 login failed
- authorities(username, auth)
Caused by: java.sql.SQLSyntaxErrorException: Table 'securitytable.users' doesn't exist
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:120)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeQuery(ClientPreparedStatement.java:1009)
at net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy.executeQuery(PreparedStatementSpy.java:780)
at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:666)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:605)
Caused by: java.sql.SQLSyntaxErrorException: Table 'securitytable.authorities' doesn't exist
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:120)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeQuery(ClientPreparedStatement.java:1009)
at net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy.executeQuery(PreparedStatementSpy.java:780)
at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:666)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:605)
- 기본적 실행되는 쿼리문은 다음과 같다. [select username,password,enabled from users where username = ?];
- 따라서 속성명을 users의 컬럼명을 username, password로 맞추지 않을 경우 아래와 같은 에러 코드가 발생한다.
- 이경우는 security-context.xml 에서 <form-login 에서 username-parameter, password-parameter를 통해서 변경 후
user-by-username-query 와 authorities-by-username-query 를 통해 Basic Query를 수정하여 사용할 수 있다.
Caused by: org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback;
bad SQL grammar [select username,password,enabled from users where username = ?];
nested exception is java.sql.SQLSyntaxErrorException: Unknown column 'username' in 'field list'
at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:234)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
at org.springframework.jdbc.core.JdbcTemplate.translateException(JdbcTemplate.java:1402)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:620)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:657)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:688)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:700)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:751)
at org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl.loadUsersByUsername(JdbcDaoImpl.java:227)
at org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl.loadUserByUsername(JdbcDaoImpl.java:184)
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:104)
... 39 more
Caused by: java.sql.SQLSyntaxErrorException: Unknown column 'username' in 'field list'
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:120)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeQuery(ClientPreparedStatement.java:1009)
at net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy.executeQuery(PreparedStatementSpy.java:780)
at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:666)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:605)
... 46 more
- 위와 같이 설정을 다시 한 후에 로그인할 경우 다시 에러가 발생한다.
- 이것은 ImMemory 방식으로 했을떄 {noop} 했던게 있는데, 이는 PasswordEncoder를 사용하지 않는다는 의미인데 JDBC연결방식을 할경우에는 PasswordEncoder를 설정해줘야 하기 때문에 아래와 같은 PasswordEncoder에러가 발생했다.
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:238)
at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:198)
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(DaoAuthenticationProvider.java:86)
at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:166)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:199)
- PasswordEncoder를 설정해주는 방법으로는
1. 사용자가 PasswordEncoder를 생성하는 방법과
- 이방법의 경우 Class를 생성 한 후 PasswordEncoder 를 상속하여 encode()와 matches()를 오버라이딩 하여 사용하면되는데 암호화 없이 바로 로그인 되도록 설정해 보도록 하겠다.
>> rawPassword의 경우는 사용자가 입력한 패스워드를 말하고, 암호화 하려면 encode() 안에서 매개변수로 받은
rawPassword를 암호화 하면된다.
>> matches() 의 경우는 입력한 비밀번호화 암호화된 encodedPassword가 동일한지 확인하는 매서드 이다.
== 현재 여기서 encode를 하지 않았기 때문에 encodedPassword는 rawPassword와 동일한상태이다.
>> 사용자가 정의한 PasswordEncoder를 BEAN으로 등록한 후에 PasswordEncoder로 등록해주면 사용할 수 있다.
2. Spring security 에서 제공하는 BCryptPasswordEncoder 를 사용하는 방법이 있다.
- 이경우의 경우 security-context.xml 에서 아래와 같이 BEAN 등록을 한후에 password-encoder로 설정해주면된다.
- 이때 로그인할때 DB에 저장되어 있는 PASSWORD는 Bcrypt에 의해 암호화된 패스워드로 저장되어 있어야
- 입력한 비밀번호화 DB에 있는 비밀번호와 match() 메서드를통해서 확인한 후에 로그인이 된다.
3. 사용자 정의 DB & spring security 연동 방식 이있다.
(DB에서 사용자 정보를 가지고 와서 사용하라는 Interface와 loadUserByUsername() 추상 메서드가 만들어져 있기 때문에 해당 인터페이스를 상속하여 해당 메서드를 오버라이딩하여 사용한다.)
- 사용자 정의 DB & Spring security 연동 방식은 userid, password 이외의 email이나 다른 속성들을 활용하여 security를 사용할 수 있는 방법으로 Spring security 에서 제공하는 UserDetailsService를 상속하는 Class를 생성하여
loadUserByUsername() 을 오버라이딩 하여 security를 구현한다. loadUserByUsername() 는 DB에서 로그인 정보를 가지고 오는 함수이다.
이때 DAO를 @Autowired로 주입해줘야 DB에 접근하여 사용자의 정보를 가지고 올 수 있다.
// service, serviceImpl 는 구현하지 않았다.
마지막으로 security-context.xml에 새로 생성한 CustomUserDetailsService를 Bean 등록하고
user-service를 지워주고 새로 등록한 CustomUserDetailsService를 ref 로 참조해준다.
다시 CustomUserDetailsService로 돌아가서 상속한 @UserDetailsService에서
@loadUserByUsername을 오버라이딩 해야 하는데 이때 리턴 타입이 UserDetails 이다.
그래서 DB에서 가지고 오는 MemberVO를 UserDetails로 변환할 수 있도록
CustomUser Class를 만들어 User Class( 상속함->UserDetails )를 상속하여 UserDetails 객체를 생성해보자
CustomUser의 생성자에서는 User의 생성자가 호출이 되는데 이때 User의 아래 생성자가 호출된다.
super() 를 통해서 CustomUser생성자에 MemberVO를 주입하면 User의 생성자에 자동으로 주입이 된다.
즉
UserDetail <- User <- CustomUser 이 관계이므로
UserDetail user = new CustomUser() 가 성립된다. 따라서 UserDetail 의 return 타입에 CustomUser를 리턴해줘도된다.
이렇게 CustomUserDetailsService를 통해서 DB에서 가지고온 내용을 UserDetail로 변환할 수 있습니다.
끝@
===========================================================================================
'dev > Spring-security' 카테고리의 다른 글
[Spring] Filter (0) | 2022.08.20 |
---|---|
[Spring] Spring Security 대략적인 흐름 (0) | 2022.08.06 |
[Spring] UserDetailsService 활용 (0) | 2022.08.05 |
[Spring ] JDBC를 이용한 인증/권한 처리 (0) | 2022.08.04 |
[Spring] Spring security - custom login page (0) | 2022.08.02 |