본문 바로가기
dev/Spring-security

[Spring] spring security 중간정리

by dev_Step 2022. 8. 6.

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 화면 또는

           개인이 설정한 로그인 화면으로 이동된다.

기본 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 는 구현하지 않았다.

==DAO
Mapper.xml
root-context.xml

마지막으로 security-context.xml에 새로 생성한 CustomUserDetailsService를 Bean 등록하고 

user-service를 지워주고 새로 등록한 CustomUserDetailsService를 ref 로 참조해준다.

bean 등록
customUserDetailsService 참조하도록 지정

 

다시 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로 변환할 수 있습니다.

 

끝@

===========================================================================================