728x90
300x250

[Spring-Framework] 17. Spring MVC, Spring Security 5.4, Oracle - 보안처리(로그인-Java) (2)


이번에는 이전 글에 이어서 Spring-Security를 구현해보려고 한다.

조금 이번 글부터는 난이도가 있어지니깐 개발 전략을 잘 숙지해서 작업하면 좋겠다.


1. [Spring-Framework] 16. Spring MVC, Spring Security 5.4, Oracle - 보안처리(로그인-Java) (1), 2020-09-27

https://yyman.tistory.com/1422



14. 개발 전략


하나 만들면 다 되는 게 아니다. 계속 연속해서 복합적으로 수정작업을 해줘야 한다.

그래서 개발 작업이 힘이 든다. 쉽지만 않다.


그림 24. 개발 전략도


SecurityWebApplicationInitializer.java, SecurityConfig.java는 Spring Security 구현에 있어서 핵심이라고 해도 무방하다.

두 개를 잘 구현한다면, 셈플 로그인 페이지는 볼 수 있다.


문제는 자바 버전으로 구현했을 때 보안 토큰 절차가 xml방식에 비해서 매우 까다롭게 반응한다는 것이다.

그래서 간단한 코드로 태스트를 해보기도 전에 DB 설계를 할 수 밖에 없었다.


이유는 토큰 인증 때문에 그렇다.


SqlMapSessionFactory도 계속 반복해서 다양한 영역에서 재사용될 것이다.



15. config의 SecurityWebApplicationInitializer.java (필수 파일)


이 파일을 보면, 제일 황당한 생각이 들 수 밖에 없는 이유가 코드는 몇 줄 안 되는데, 없으면 동작이 안 된다는 것이다.


package com.springMVC.javaSecurity5.config;


import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;


public class SecurityWebApplicationInitializer 

                        extends AbstractSecurityWebApplicationInitializer {


}


파일명: SecurityWebApplicationInitializer.java


[첨부(Attachments)]

SecurityWebApplicationInitializer.zip



16. config의 SecurityConfig.java (필수 파일)


"SecurityConfig.java" 이 파일도 없으면 Spring Security with 자바 버전이 동작되지 않는다.


패키지 경로: com.springMVC.javaSecurity5.config


package com.springMVC.javaSecurity5.config;


import javax.sql.DataSource;


import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;

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.configuration.WebSecurityConfigurerAdapter;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import org.springframework.security.crypto.password.PasswordEncoder;

import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;

import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import org.springframework.security.web.csrf.CsrfFilter;

import org.springframework.web.filter.CharacterEncodingFilter;


import com.springMVC.javaSecurity5.db.SqlMapSessionFactory;

 

@Configuration

@EnableWebSecurity

public class SecurityConfig extends WebSecurityConfigurerAdapter {

 

    @Autowired

    PasswordEncoder passwordEncoder;

    

    @Autowired

    private CustomAuthenticationProvider authProvider;

    

    protected void configure(AuthenticationManagerBuilder auth, HttpSecurity http) throws Exception {

      

    CharacterEncodingFilter filter = new CharacterEncodingFilter();


    /* UTF-8 한글 보완 */

        filter.setEncoding("UTF-8");

        filter.setForceEncoding(true);

        http.addFilterBefore(filter,CsrfFilter.class);

   

        auth

        .authenticationProvider(authProvider);

        

    /* 현재 - 임시

        auth.inMemoryAuthentication()

        .passwordEncoder(passwordEncoder)

        .withUser("user").password(passwordEncoder.encode("1234")).roles("RULE_USER")

        .and()

        .withUser("admin").password(passwordEncoder.encode("1234")).roles("RULE_USER", "RULE_ADMIN");

        

         */

        

        // withUser("admin").password(passwordEncoder.encode("1234")).roles("USER", "ADMIN");

      

        /* 임시

   

    UserBuilder users = User.withDefaultPasswordEncoder();

      auth.inMemoryAuthentication()

        .withUser(users.username("admin").password("1234").roles("USER"))

        .withUser(users.username("user").password("1234").roles("ADMIN"));

    */

    }

 

 

    @Override

    protected void configure(HttpSecurity http) throws Exception {


        http.authorizeRequests()

        

        // index

        .antMatchers("/")

            .permitAll()


         // 접근 오류

     .antMatchers("/member/accessDenied")

         .permitAll()

         

         .antMatchers("/member/accessDeniedView")

         .permitAll()    


         // 회원 로그인 기능

     .antMatchers("/member/loginForm")

         .permitAll()

         

     // 관리자 페이지 기능

        .antMatchers("/admin/**")

        .hasRole("ADMIN")

        // "RULE_ADMIN이라고 DB에 입력되어 있다면, RULE_은 제거하고 입력해야 인식함."

        

        // 폼 로그인 명세

        .and()

            .formLogin()

        .permitAll()

                .loginPage("/member/loginForm")

                .failureForwardUrl("/member/loginForm?error")

                .defaultSuccessUrl("/")

                .usernameParameter("id")

                .passwordParameter("password")

       

        // 로그아웃 처리

.and()

                .logout()

                .logoutUrl("/logout")

                .logoutSuccessUrl("/")

                .invalidateHttpSession(true)

                .deleteCookies("JSESSION_ID")

                .deleteCookies("remember-me")

        // 로그인 

            .and()

            .rememberMe()

            .tokenValiditySeconds(604800)

            .tokenRepository(persistentTokenRepository())

            // 예외처리(

            .and()

        .exceptionHandling()

        .accessDeniedPage("/member/accessDenied")

        // csrf 설정

            .and()

                .csrf().disable();

       

        

            

        

    /*

        http.authorizeRequests()

        .antMatchers("/login")

            .permitAll()

        .antMatchers("/**")

            .hasAnyRole("ADMIN", "USER")

            .hasAnyAuthority("RULE_ADMIN", "RULE_USER")

        .and()

            .formLogin()

            .loginPage("/login")

            .defaultSuccessUrl("/")

            .failureUrl("/login?error=true")

            .permitAll()

            

            .loginPage("/login")

            .usernameParameter("email")

            .passwordParameter("password")

            .successHandler(successHandler())      

            .failureHandler(failureHandler())

            .permitAll();

            

        .and()

            .logout()

            .logoutSuccessUrl("/login?logout=true")

            .invalidateHttpSession(true)

            .permitAll()

        .and()

            .csrf()

            .disable();

    */

        

    }

    

    // 로그아웃 Persistent_Logins에 관한 설정   (주석 해도 무방)...

    @Bean 

public PersistentTokenRepository persistentTokenRepository() {

JdbcTokenRepositoryImpl db = new JdbcTokenRepositoryImpl();

DataSource usrDS = getDataSource();

db.setDataSource(usrDS);

return db;

}

    


    // DataSource 불러오기    (주석 해도 무방)

@Bean

public DataSource getDataSource() {

       // BasicDataSource dataSource = new BasicDataSource(); - Apache DBCP2

SqlMapSessionFactory factory = SqlMapSessionFactory.getInstance();

       return factory.getOracleDataSource(); // 오라클 적용함.

}

    

// 비밀번호 생성 - 암호(BCryptPasswordEncoder)

    @Bean

    public PasswordEncoder passwordEncoder() {

        return new BCryptPasswordEncoder();

        

    }

    

}


파일명: SecurityConfig.java


[첨부(Attachments)]

SecurityConfig.zip



비고: 주석 잘 쳐서 정리해서 빌드해보면, 내장 로그인 페이지를 볼 수 있다.
       - SecurityWebApplicationInitializer.java, SecurityConfig.java 두 개 파일의 힘이 얼마나 큰지 실감 해볼 수 있다.

         Spring-Framework 설정 전체를 제어해버린다고 해도 된다.




17. 로그인 인증 - Spring Security (CustomAuthenticationProvider.java)


이 코드 부분은 찾아보려고 해도 쉽게 나오지 않는다. 어려운 부분 중 하나이다.

공개하는 이유는 삽질을 적게 하라는 의미이다.


패키지 경로: com.springMVC.javaSecurity5.config


package com.springMVC.javaSecurity5.config;


import java.util.List;


import org.springframework.security.authentication.AuthenticationProvider;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

import org.springframework.security.core.Authentication;

import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.core.userdetails.UserDetails;

import org.springframework.security.core.userdetails.UserDetailsService;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import org.springframework.stereotype.Component;


import com.springMVC.javaSecurity5.service.CustomUserDetailsService;


@Component

public class CustomAuthenticationProvider implements AuthenticationProvider {

    

    private UserDetailsService userDeSer;

 

    @Override

    public Authentication authenticate(Authentication authentication) {

        

        String username = (String) authentication.getPrincipal();

        String password = (String) authentication.getCredentials();

        

        if ( username.equals("fail")) {

        System.out.println("(에러)아이디: 실패");

        return null;

        }

        

        // DB 정보 읽기

        userDeSer = new CustomUserDetailsService();

        UserDetails userDetail = userDeSer.loadUserByUsername(username);

        

        @SuppressWarnings("unchecked")

  List<GrantedAuthority> roles = (List<GrantedAuthority>) userDetail.getAuthorities();

        

        // 권한

        System.out.println("DB불러오기-권한:" + userDetail.getAuthorities());

        System.out.println("DB불러오기-비밀번호:" + userDetail.getPassword());

        System.out.println("roles:" + roles.get(0));

        

        if ( !matchPassword(password, userDetail.getPassword())) {

        System.out.println("(에러)비밀번호: 불일치" + password);

        return null;

        }

        

        UsernamePasswordAuthenticationToken result =

        new UsernamePasswordAuthenticationToken(username, password, roles);

        

        result.setDetails(userDetail);

        

        return result;

    }

 

    @Override

    public boolean supports(Class<?> authentication) {

        return true;

    }

    

    private boolean matchPassword(String loginPwd, String password) {

   

        BCryptPasswordEncoder secure = new BCryptPasswordEncoder();

        return secure.matches(loginPwd, password);

    }

 

}


파일명: CustomAuthenicationProvider.java


[첨부(Attachments)]

CustomAuthenticationProvider.zip


어디에 구체적으로 사용되는 부분인가?


사용하는 영역은 SecurityConfig.java에 http의 auth의 .authenicationProvider()에 사용된다.



그림 25. SecurityConfig.java


왜 이 코드를 사용하는지 소개해본다면,

"No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken"

이 문제가 디버그 오류창에 뜨는 것을 볼 수 있다.


기본 내장형 계정 생성 등으로 인증을 시도하면 xml방식에서는 처리해줬는데, java방식에서는 인증받지 못한다.


     /* 현재 - 임시


        auth.inMemoryAuthentication()

        .passwordEncoder(passwordEncoder)

        .withUser("user").password(passwordEncoder.encode("1234")).roles("RULE_USER")

        .and()

        .withUser("admin").password(passwordEncoder.encode("1234")).roles("RULE_USER", "RULE_ADMIN");

        

        */


[문제가 발생되는 기본형 - 코드]


이 코드로 작업하면, xml에서는 동작되었던 부분이 동작되질 않는다.


토큰 인증도 해결할 겸 "CustomAuthenicationProvider.java를 설계해서 문제를 해결한 것이다.




18. SQL (Factory) - SqlMapSessionFactory.java


나중에 CP(Connection Pool, 커넥션 풀)이라고 불리는 것으로 구현해봐도 좋을 듯 싶다.

iBatis를 남용해서 프로젝트에 기본마냥 소개하는 책들이 무척 많은데 기본은 순수한 DB를 사용하는 것부터 출발하는 것이다.


iBatis는 SQL 코드 개발 등에서 생산성이 좋아지는 도구 중 하나이지만, 필수 사항은 아니라고 본다.

차라리 필수 사항을 꼽아본다면, 커넥션 풀을 하나 추천해보고 싶다.


아무튼 커넥션 풀 주제가 아니기 때문에 생략한다.


패키지 경로: com.springMVC.javaSecurity5.db


package com.springMVC.javaSecurity5.db;


import java.io.IOException;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.util.Properties;


import javax.sql.DataSource;


import oracle.jdbc.pool.OracleDataSource;


public class SqlMapSessionFactory {

private static SqlMapSessionFactory factory = new SqlMapSessionFactory();


private SqlMapSessionFactory() {}


private final String driverName = "oracle.jdbc.driver.OracleDriver";

private final String dbUrl = "jdbc:oracle:thin:@127.0.0.1:1521:orcl";

private final String userName = "{사용자계정명}";

private final String userPassword = "{비밀번호}";

public static SqlMapSessionFactory getInstance() {

return factory;

}


/*

*     public static DataSource getMySQLDataSource() {

        Properties props = new Properties();

        FileInputStream fis = null;

        MysqlDataSource mysqlDS = null;

        try {

            fis = new FileInputStream("db.properties");

            props.load(fis);

            mysqlDS = new MysqlDataSource();

            mysqlDS.setURL(props.getProperty("MYSQL_DB_URL"));

            mysqlDS.setUser(props.getProperty("MYSQL_DB_USERNAME"));

            mysqlDS.setPassword(props.getProperty("MYSQL_DB_PASSWORD"));

        } catch (IOException e) {

            e.printStackTrace();

        }

        return mysqlDS;

    }

    */

    public DataSource getOracleDataSource(){

        

    OracleDataSource oracleDS = null;

        

    try {

            oracleDS = new OracleDataSource();

            oracleDS.setURL(dbUrl);

            oracleDS.setUser(userName);

            oracleDS.setPassword(userPassword);

        } catch (SQLException e) {

            e.printStackTrace();

        }

        return oracleDS;

        

    }

public Connection connect() {


Connection conn = null;


try {

Class.forName(driverName);

conn = DriverManager.getConnection(dbUrl, userName, userPassword);

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}


return conn;


}


public void close(Connection conn, PreparedStatement ps, ResultSet rs) {


if ( rs != null ) {


try {

rs.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}


close(conn, ps); // Recursive 구조 응용(재귀 함수)


} // end of if


}


public void close(Connection conn, PreparedStatement ps) {


if (ps != null ) {

try {

ps.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}

} // end of if


if (conn != null ) {

try {

conn.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}

} // end of if


}

}



파일명: SqlMapSessionFactory.java


[첨부(Attachments)]

SqlMapSessionFactory.zip



비고: 재사용이 가능한 형태로 설계하였다.



19. Model - CustomUserDetails.java


순수한 Model 형태는 아니고, 부분 개량하였다.

Spring-Security에서 제공하는 UserDetails(인터페이스)를 Model 클래스에 구현해야 한다.

List<role> 기능 문제 등으로 인해서 String authorities를 List<GrantedAuthority>로 변경하였다.


@Override 된 부분들이 UserDetails에 정의된 내용이다.


패키지 경로: com.springMVC.javaSecurity5.model


package com.springMVC.javaSecurity5.model;


import java.util.ArrayList;

import java.util.Collection;

import java.util.List;


import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.core.authority.SimpleGrantedAuthority;

import org.springframework.security.core.userdetails.UserDetails;


public class CustomUserDetails implements UserDetails{


private static final long serialVersionUID = 1L;

private String username;

    private String password;

    // 개량함. (다중 권한 고려)

    private List<GrantedAuthority> authorities;

    private boolean enabled;

        

public String getUsername() {

return username;

}


public void setUsername(String username) {

this.username = username;

}


public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

     public void setAuthority(String authority) {

// 권한 객체 생성

if ( authorities == null ) {

authorities = new ArrayList<GrantedAuthority>();

}

// 권한 추가

SimpleGrantedAuthority grantObj = new SimpleGrantedAuthority(authority);

authorities.add(grantObj);

     }


public boolean getEnabled() {

return enabled;

}

public void setEnabled(boolean enabled) {

this.enabled = enabled;

}


@Override

public Collection<? extends GrantedAuthority> getAuthorities() {


        return authorities;


}


@Override

public boolean isAccountNonExpired() {

// TODO Auto-generated method stub

return false;

}


@Override

public boolean isAccountNonLocked() {

// TODO Auto-generated method stub

return false;

}


@Override

public boolean isCredentialsNonExpired() {

// TODO Auto-generated method stub

return false;

}


@Override

public boolean isEnabled() {

// TODO Auto-generated method stub

return false;

}

    

}


파일명: CustomUserDetails.java


[첨부(Attachments)]

CustomUserDetails.zip





20. Service - CustomUserDetailsService.java


CustomUserDetailsService는 Spring-Security의 "UserDetailsService(인터페이스)"로 정의된 내용을 구현하는 것이다.

인터페이스의 영향도 있지만, DB를 실제로 불러올 때 사용자 관점에서 처리되는 부분이라고 본다.


package com.springMVC.javaSecurity5.service;


import org.springframework.security.core.userdetails.UserDetailsService;

import org.springframework.security.core.userdetails.UsernameNotFoundException;


import com.springMVC.javaSecurity5.dao.SqlSessionTemplate;

import com.springMVC.javaSecurity5.model.CustomUserDetails;


public class CustomUserDetailsService implements UserDetailsService {

    

    private SqlSessionTemplate sqlSession = SqlSessionTemplate.getInstance();

 

    public CustomUserDetails getUserById(String username) {

        return sqlSession.selectOne("user.selectUserById", username);

    }

 

    public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {


    CustomUserDetails user = sqlSession.selectOne("null", username);

        

        if(user==null) {

            throw new UsernameNotFoundException(username);

        }

        return user;

    }

    

}


파일명: CustomUserDetailsService.java


[첨부(Attachments)]

CustomUserDetailsService.zip




21. DAO - SqlSessionTemplate.java


실제 DB를 구현하는 부분이다.


package com.springMVC.javaSecurity5.dao;


import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;


import com.springMVC.javaSecurity5.db.SqlMapSessionFactory;

import com.springMVC.javaSecurity5.model.CustomUserDetails;


public class SqlSessionTemplate {


private SqlSessionTemplate() {}

private static SqlSessionTemplate sqlTemplate;

    private static SqlMapSessionFactory session; 

    

    public static SqlSessionTemplate getInstance(){

   

        if(sqlTemplate == null){

        sqlTemplate = new SqlSessionTemplate();

            session = SqlMapSessionFactory.getInstance();

        }


        return sqlTemplate;

    }

    

// 추후 iBatis 고려

public CustomUserDetails selectOne(String id, String username) {

Connection conn = null;

    PreparedStatement pstmt = null;

    ResultSet rs = null;

   

    CustomUserDetails node = null;


    String sql = "select g1.username, g1.password, g2.authority, " + 

       "g1.enabled from comp_users g1, comp_authorities g2 where g1.username = g2.username " +

       "and g1.username = ?";


    System.out.println(sql);


    try {


    conn = session.connect();


    pstmt = conn.prepareStatement(sql);

    pstmt.setString(1, username);

   

    rs = pstmt.executeQuery();


    while ( rs.next() ) {

   

    // 데이터가 존재할 때, 노드 생성

    node = new CustomUserDetails();

    node.setUsername(rs.getNString(1));

    node.setPassword(rs.getNString(2));

    node.setAuthority(rs.getNString(3));

    System.out.println("rs:" + rs.getNString(3));

    node.setEnabled(rs.getBoolean(4));

    }


    }catch(Exception ex) {

    System.out.println("오류 발생: " + ex);

    }

    finally {

    session.close(conn, pstmt, rs);

    }


    return node;

}

}



파일명: SqlSessionTemplate.java


[첨부(Attachments)]

SqlSessionTemplate.zip



* 3부에서는 View에 대해서 구현하는 방법을 소개하겠다.


3부에서는 "jsp 파일" 등 사용자 인터페이스 화면에 대해서 소개하겠다.


1. [Spring-Framework] 17. Spring MVC, Spring Security 5.4, Oracle - 보안처리(로그인-Java) (3), 2020-09-27

https://yyman.tistory.com/1424


반응형
728x90
300x250

[Spring-Framework] 14. Spring MVC, Spring Security 5.4, Oracle - 보안처리(로그인-XML) (2)


1부에 이어서 글을 작성하도록 하겠다.


1. [Spring-Framework] 14. Spring MVC, Spring Security 5.4, Oracle - 보안처리(로그인-XML) (1), 2020-09-26

https://yyman.tistory.com/1419




11. Controller - HomeController.java


HomeController.java에 관한 것이다.

파일은 처음 프로젝트(이하 "Spring MVC Project")를 생성하면, 자동으로 만들어진다.


package com.web.springsecurity5.controller;


import java.text.DateFormat;

import java.util.Date;

import java.util.Locale;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.security.core.context.SecurityContextHolder;

import org.springframework.security.core.userdetails.UserDetails;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;


@Controller

public class HomeController {

private static final Logger logger = LoggerFactory.getLogger(HomeController.class);

/**

* Simply selects the home view to render by returning its name.

*/

@RequestMapping(value = "/", method = RequestMethod.GET)

public String home(Locale locale, Model model) {

logger.info("Welcome home! The client locale is {}.", locale);

Date date = new Date();

DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);

String formattedDate = dateFormat.format(date);

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

String username = null;

String password = null;

try {

UserDetails userDetails = (UserDetails)principal;

username = userDetails.getUsername();

password = userDetails.getPassword();

}catch(Exception e) {

e.getStackTrace();

}


model.addAttribute("username", username);

model.addAttribute("password", password);

model.addAttribute("serverTime", formattedDate );

return "home";

}

}



파일명: HomeController.java


[첨부(Attachments)]

HomeController.zip


비고: 의외로 Controller 코드는 간단하게 되어있는 것을 알 수 있다.




12. Controller - MemberController.java


MemberController에 관한 것이다.

자세히 코드를 살펴보면, "@RequestMapping"을 변경해봐도 되는 부분이 있다. (크게 무방하다.)

함수에 직접 경로를 입력한 형태로 servlet을 구성하였다.


package com.web.springsecurity5.controller;


import java.util.Locale;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;


@Controller

public class MemberController {

private static final Logger logger = LoggerFactory.getLogger(MemberController.class);

@RequestMapping(value = "/member/loginForm", method = RequestMethod.GET)

public String loginForm(Locale locale, Model model) {

logger.info("안녕 - 로그인 폼(Hello - Login Form");

// model.addAttribute("serverTime", formattedDate );

return "member/loginForm";

}


@RequestMapping(value = "/member/accessDenied")

public String accessDenied(Locale locale, Model model) {


logger.info("접근 금지 - 이동(Accessed Denied)");

// model.addAttribute("serverTime", formattedDate );

return "redirect:/member/accessDeniedView";

}

@RequestMapping(value = "/member/accessDeniedView")

public String accessDeniedView(Locale locale, Model model) {


logger.info("접근 금지 - 출력(Accessed Denied)");

// model.addAttribute("serverTime", formattedDate );

return "member/accessDenied";


}

}


파일명: MemberController.java


[첨부(Attachments)]

MemberController.zip




13. Controller - AdminController.java


관리자 페이지에 관한 Controller이다.


package com.web.springsecurity5.controller;


import java.util.Locale;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;


@Controller

public class AdminController {

private static final Logger logger = LoggerFactory.getLogger(AdminController.class);

@RequestMapping(value = "/admin/home", method = RequestMethod.GET)

public String home(Locale locale, Model model) {

logger.info("Welcome - 관리자 페이지(Admin Home)!");

return "admin/home";

}

}


파일명: AdminController.java


[첨부(Attachments)]

AdminController.zip




14. View - 구성도


출력되는 화면에 관한 프로젝트 구성도이다.



그림 25. view 프로젝트 구성도





15. View - home.jsp


코드와 실제 디자인 화면을 동시에 소개하겠다.


경로: src/main/java/webapp/WEB-INF/views


그림 26. 로그인 페이지(home.jsp) - 로그인 전



그림 27. 로그인 페이지(home.jsp) - 로그인 후





그림 27. 로그인 페이지(home.jsp) - 로그인 후 - 비밀번호 생성 페이지


비고: UI(이하 "User Interface")설계하는데 도움이 되었으면 한다.



<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>


<%@ page session="false" %>

<html>

<head>

<title>Spring-Security 5 (XML 방식)</title>

<meta charset="UTF-8">

<style>

body{

font-family:'Arial';

font-size:12px;

}

a{

text-decoration:none;

color:#666;

}

</style>

</head>

<body>

<h1>

Hello world!(Spring-Security 5(XML 방식)) - DB연동(Oracle)

</h1>

<hr />

<sec:authorize access="isAnonymous()">

<!-- 로그인 전 -->

<p>

<a href="<c:url value="/member/loginForm" />">로그인</a>

</p>

</sec:authorize> 


<sec:authorize access="isAuthenticated()">

<!-- 로그인 성공 -->

<form:form action="${pageContext.request.contextPath}/logout" method="POST">

<input type="submit" value="로그아웃" />

</form:form>

<p>

${username} &nbsp; ${password}

</p>

</sec:authorize> 


<h3>

<a href="<c:url value="/admin/home" />">관리자 홈</a>&nbsp;&nbsp;

<a href="<c:url value="/encode-password?password=pass" />">비밀번호</a>

</h3>


<!-- 비밀번호 생성기 -->

<c:set var="gene_pwd" value="${encode}" />

<c:if test="${gene_pwd != null}">

    <c:out value="${gene_pwd}" />

</c:if>


</body>

</html>



파일명: home.jsp


[첨부(Attachments)]

home.zip



16. View - admin/home.jsp


관리자 페이지에 관한 것이다.


경로: src/main/java/webapp/WEB-INF/views/admin



그림 28. 관리자 홈 화면 - (User-Interface) 설계


<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<!DOCTYPE html>

<html lang="ko">

<head>

    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

    <title>관리자(ROLE_ADMIN) - 홈</title>

    <style>

    body{

    font-family:'Arial';

    font-size:12px;

    }

   

    a{

    text-decoration:none;

    color:#666;

    }

    </style>

</head>


<body>


<h1>관리자 홈 화면!</h1>

<hr />


<h3>[<a href="<c:url value="/" />">홈으로(Home)</a>]</h3>


</body>

</html>


파일명: home.jsp


[첨부(Attachments)]

home.zip




17. View - member/loginForm.jsp


로그인 페이지에 대한 것이다.


경로: src/main/java/webapp/WEB-INF/views/member



그림 29. 로그인 폼 - 화면



그림 30. 로그인 폼 - 계정 불일치(아이디 또는 비밀번호)



<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<!DOCTYPE html>

<html lang="ko">

<head>

<meta charset="UTF-8">

    <title>로그인 - 페이지(Login - Page)</title>

    <style>

    body{

    font-family:'Arial';

    font-size:12px;

    }

   

    a{

    text-decoration:none;

    color:#666;

    }

   

    </style>

</head>


<body>


<h1>아이디와 비밀번호를 입력해주세요.</h1>

<hr />


<c:url value="/login" var="loginUrl" />


<form:form name="f" action="${loginUrl}" method="POST">

    <p>

        <label for="username">아이디</label>

        <input type="text" id="id" name="id" />

    </p>

    <p>

        <label for="password">비밀번호</label>

        <input type="password" id="password" name="password"/>

    </p>

    <%-- <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> --%>

    <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

    <button type="submit" class="btn">로그인</button>

    

    <!-- 에러 메시지 영역 -->

    <c:if test="${param.error != null}">

        <p>아이디와 비밀번호가 잘못되었습니다.</p>

    </c:if>

    <c:if test="${param.logout != null}">

        <p>로그아웃 하였습니다.</p>

    </c:if>

    

</form:form>

<h3>[<a href="<c:url value="/" />">홈으로(Home)</a>]</h3>


</body>

</html>



파일명: loginForm.jsp


[첨부(Attachments)]

loginForm.zip




18. View - member/accessDenied.jsp


인가되지 않은 화면 또는 오류 처리에 대한 것이다.



그림 31. Access Denied 페이지


<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<!DOCTYPE html>

<html lang="ko">

<head>

    <meta charset="UTF-8">

    <title>Access Denied</title>

</head>


<body>


<h1>Access Denied!</h1>


<h3>[<a href="<c:url value="/" />">홈</a>]</h3>


</body>

</html>



파일명: accessDenied.jsp


[첨부(Attachments)]

accessDenied.zip



19. 공식 사이트에서 배포하고 있는 Spring-Boot 기반 - 예제


https://github.com/spring-projects/spring-security/tree/5.4.0/samples/boot/helloworld


Spring Security 5.4를 자바 파일 환경 설정 방식으로 구현한 것이다.

(구현 방식에는 크게 "XML 방식"과 "Java 파일" 방식이 있다.)

-> 구현하는데 있어서, 차이점이 있다.



그림 32. github - Spring Security Official 사이트



* 맺음글(Conclusion)


아무쪼록 Spring-Security 5 적용에 있어서 어려움이 해소되길 진심으로 기원한다.



* 참고자료(References)


1. 스프링프레임웍 - Spring Security(2) : 커스텀 로그인 화면 및 권한에 따른 접근 제어, https://offbyone.tistory.com/91, Accessed by 2020-09-26, Last Modified 2018-04-08.

-> 추천(50점): 잘 되어 있는 블로그 중 하나이다.


2. 스프링 시큐리티 5 - There is no PasswordEncoder mapped for the id "null", https://meaownworld.tistory.com/129, Accessed by 2020-09-26, Last Modified 2018-03-11.


3. [spring security/스프링 시큐리티] 비밀번호 암호화와 로그인 하기, https://simsimjae.tistory.com/36, Accessed by 2020-09-26, Last Modified 2017-08-28.


4. BCryptPasswordEncoder : 암호 해시, http://www.devkuma.com/books/pages/1124, Accessed by 2020-09-26, Last Modified 2017-12-30.

-> 비고: Spring Security 암호 처리에 대해서 잘 소개하고 있음.


5. Spring Security JdbcDaoImpl Example, https://www.concretepage.com/spring-5/spring-security-jdbcdaoimpl#XML, Accessed by 2020-09-26, Last Modified 2019-12-11.

-> 추천(35점): Java방법론, XML방법론 두 타입을 자세하고 잘 설명하고 있음.


6. [Spring Security] 현재 로그인한 사용자 정보 가져오기, https://itstory.tk/entry/Spring-Security-%ED%98%84%EC%9E%AC-%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%95%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%A0%95%EB%B3%B4-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0, Accessed by 2020-09-26, Last Modified 2017-09-07.


7. [spring] 시큐리티 비밀번호 bcrypt 간단한 암호화, https://blog.naver.com/PostView.nhn?blogId=ljpark6&logNo=221578990976, Accessed by 2020-09-26, Last Modified 2019-07-06.


9. Spring Web MVC Security Basic Example Part 1 with XML Configuration, https://www.codejava.net/frameworks/spring/spring-web-mvc-security-basic-example-part-1-with-xml-configuration, Accessed by 2020-09-26, Last Modified 2019-06-24.

-> 비고: Spring Security 4를 기준으로 작성하였음. 

Spring-Framework 3.2~4.x 이전의 경우에는 내장 로그인 인터페이스가 해당 사이트와 동일하거나 흡사함.

이전 인터페이스보다는 Spring-Framework 4.x~5 이후부터는 디자인이 있는 형태의 내장 로그인 인터페이스로 출력됨.


10. [SPRING] Controller에서 redirect하기, https://sime.tistory.com/119, Accessed by 2020-09-26, Last Modified 2016-11-17.

-> 비고: Spring-Security의 Access-error-page처리에 있어서 메시지만 출력하고, accessDenied.jsp화면이 출력되지 않는 문제를 해결하는데

          redirect 방법을 참고하여 적용하였음.


11. Spring Security 5.4 - Docs, https://docs.spring.io/spring-security/site/docs/5.4.0/reference/html5/, Accessed by 2020-09-26, Last Modified 2020-09-09.

-> 비고: 공식 사이트이긴 하나, spring-boot을 주력으로 작성되어 있어서 크게 도움이 되지 않을 수도 있음.


12. Spring Security 5.4 - API, https://docs.spring.io/spring-security/site/docs/5.4.0/api/, Accessed by 2020-09-26, Last Modified .

-> 비고: API 사이트이니 구현해놓고 분석할 때 참고하면 될 것으로 보인다.


13. spring-security/samples/boot/helloworld at 5.4.0 · spring-projects/spring-security · GitHub, https://github.com/spring-projects/spring-security/tree/5.4.0/samples/boot/helloworld, Accessed by 2020-09-26, Last Modified .

-> 추천(15점): 간결하게 Java 환경설정 방식으로 예제를 소개하고 있다. (Spring-boot로 작성), Spring Security에서 공식 운영하는 github사이트이다.

반응형
728x90
300x250

[Spring-Framework] 13. Spring MVC, Spring Security 5.4, Oracle - 보안처리(로그인-XML) (1)


많은 고민과 엄선하여 2020-09월 기준의 최신 프레임워크를 적용한 Spring Security 5.4 프로젝트를 소개하려고 한다.

이전의 돌아다니는 방법으로 Spring Security를 학습하려고 한다면, 많은 오류부터 경험하게 될 가능성이 높아서 수 많은 실험 끝에 완성하였다.


인터넷 검색 등을 시도해보면, "Spring Boot"를 적용하면 훨씬 쉽고 만들 수 있다는 가능성에 대한 글들을 많이 보았다.


순수한 Spring-Framework에 대한 관련 주제를 담은 글의 비중은 매우 적어서 수 차례 연구를 완료한 후에 글을 작성하게 되었다.


[작업환경]

[WAS(Web Application Server), 웹 애플리케이션 서버]

1. apache-tomcat-9.0.37-windows-x64


[DBMS(DataBase Management System - Tools]

2. sqldeveloper-19.2.1.247.2212-x64


[DB(DataBase)]

3. Oracle Databases 19.3.0.0


[IDE(Integration Development Environment)]

4. Spring-Tool Suites 4-4.7.2. Releases.


[Framework(프레임워크)]

5. spring-security-taglibs(5.4)

6. spring-security-config(5.4)

7. spring-security-web(5.4)

8. spring-security-core(5.4)

9. javax.servlet-api(4.0.1)

10. spring-webmvc(5.2.9.RELEASE)

11. spring-context(5.2.9.RELEASE)
12. Maven 3.6.3/1.16.0.20200610-1735


[Java]

OpenJDK-14.0.2 (https://openjdk.java.net/)


원리나 배경 등 이런 것도 물론 중요하나 Spring Security는 다소 프로젝트 위주로 경험 후에 터득하는 것이 더 적합할 것으로 보인다.


* 다음 주제 - (자바 버전으로 구현)

1. [Spring-Framework] 16. Spring MVC, Spring Security 5.4, Oracle - 보안처리(로그인-Java) (1)
https://yyman.tistory.com/1422


2. [Spring-Framework] 17. Spring MVC, Spring Security 5.4, Oracle - 보안처리(로그인-Java) (2)

https://yyman.tistory.com/1423


3. [Spring-Framework] 18. Spring MVC, Spring Security 5.4, Oracle - 보안처리(로그인-Java) (3)

https://yyman.tistory.com/1424



1. 결과


어떤 프로젝트를 하는지 소개하려고 한다. 지금 구성하는 화면은 스프링 시큐리티의 "커스텀 로그인"이라는 것이다.

커스텀 로그인 코드만 주석으로 정리해보면, 내장된 로그인 화면을 살펴볼 수 있다.



그림 1. 결과(로그인 폼) - 로그인 전




그림 2. 결과(로그인 폼) - 계정 정보의 오류




그림 3. 관리자 페이지 - "권한이 없는 사용자가 접속했을 경우"




그림 4. 로그인된 화면




그림 5. 비밀번호 랜덤 암호(bcrypt 기법 적용)



그림 6. 로그인된 화면 - "user 계정"




그림 7. 권한이 있는 사용자 - 관리자 홈 접속 화면



그림 7-1. 접근 제한 페이지 출력




1-1. 결과 - 프로젝트 구성도


어떤 형태로 프로젝트가 구성되어있는지 소개하겠다.

작업해야 할 양이 조금 많다.



그림 8. 프로젝트 구성도





2. 데이터베이스 설계


데이터베이스 기능도 지원한다. 데이터베이스를 사용 안하는 방법에 대해서도 소개하고 있으니 참고하면 되겠다.


데이터베이스 설계 기준은 Spring Security에서 제공하는 계정, 권한, 그룹 시스템 로직을 바탕으로 작성하였다.

데이터베이스 기준으로 보면, 스프링 시큐리티는 하위버전부터 현재까지 큰 차이는 없다.


참고: 기본적인 기능으로 데이터베이스 설계를 하지 않고도 Spring Security를 적용하여 사용할 수도 있다.


그림 9. Membership - ERD 설계도




그림 10. "comp_users" 테이블



그림 11. "comp_authorities" 테이블



그림 12. "comp_groups" 테이블



그림 13. "comp_group_members" 테이블



그림 14. "comp_group_authorities" 테이블



3. SQL (Create table)


SQL로 작업해야 할 테이블에 대해서 기술하였으니 참고하면 도움이 되겠다.


[암호화된 암호]

계정: user -> 임시비밀번호: password

계정: admin -> 임시비밀번호: pass


CREATE TABLE comp_users (

username VARCHAR(50) NOT NULL,

password VARCHAR(300) NOT NULL,

enabled INT NOT NULL,

PRIMARY KEY (username)

);


CREATE TABLE comp_authorities (

  username VARCHAR(50) NOT NULL,

  authority VARCHAR(50) NOT NULL,

  CONSTRAINT fk_authorities_users FOREIGN KEY (username) REFERENCES comp_users (username)

); 


CREATE TABLE comp_groups(

id VARCHAR2(20) NOT NULL,

group_name VARCHAR2(20) NULL

);


CREATE TABLE comp_group_authorities(

group_id VARCHAR2(20) NOT NULL,

authority VARCHAR2(20) NOT NULL

);


CREATE TABLE comp_group_members(

group_id VARCHAR2(20) NOT NULL,

username VARCHAR2(20) NOT NULL

);



-- 계정

INSERT INTO comp_users (username, password, enabled) VALUES ('user', '$2a$10$x04djNV2e9rpcPPRyXoLk.rMm6iZe2/vYdzpqHQcLeNSYdt7kc30O', 1);

INSERT INTO comp_users (username, password, enabled) VALUES ('admin', '$2a$10$QUddY3O/6ZgkYCR6MFlv9.nqA501Fm0cc/ZxQHX5pwb1o0CYCTiIS', 1);


-- 사용자 권한

INSERT INTO comp_authorities (username, authority) VALUES ('user', 'ROLE_ADMIN');

INSERT INTO comp_authorities (username, authority) VALUES ('admin', 'ROLE_USER');


-- 그룹

INSERT INTO comp_groups (id, group_name) VALUES ('G01', '관리자 그룹');

INSERT INTO comp_groups (id, group_name) VALUES ('G02', '사용자 그룹');


-- 그룹 권한

INSERT INTO comp_group_authorities (group_id, authority) VALUES ('G01', 'ROLE_ADMIN');

INSERT INTO comp_group_authorities (group_id, authority) VALUES ('G01', 'ROLE_USER');

INSERT INTO comp_group_authorities (group_id, authority) VALUES ('G02', 'ROLE_USER');


-- 그룹 회원

INSERT INTO comp_group_members (group_id, username) VALUES ('G01', 'user');

INSERT INTO comp_group_members (group_id, username) VALUES ('G02', 'admin');



파일명: sampleDb-oracledb.sql


[첨부(Attachments)]

sampleDb-oracledb.zip





4. 프로젝트 생성하기


그림으로 자세하게 표현하고 있는 이유는 Spring-Framework 순수한 프로젝트로 초기 개발환경을 셋팅하려고 하면 정말 많은 시간이 소요된다.

그리하여 매우 자세하게 소개하고 있으니 참고하면 도움이 될 것 같다.


비고: Spring Legacy Project가 보이지 않는다면, Help->Eclipse Marketplace에서 "STS"를 검색하여 AddOn을 설치하면 된다.



그림 15. Eclipse Marketplace의 화면


Spring Tools 3 Add-On for Spring Tools 4 3.9.14.RELEASES를 설치해주면 된다.





그림 16. Spring Legacy Project 생성하기


SLP(Spring Legacy Project)의 Spring MVC Project는 기본 버전이 매우 낮은 "3.2.2 RELEASES"이다.


비고: pom.xml을 변경해주면, 현재의 최신 프레임워크 버전으로 변경할 수 있다.



그림 17. Spring Legacy Project 생성하기


프로젝트명을 지정한 후 Next를 누른다.



그림 18. Spring Legacy Project 생성하기


상위 경로를 입력한 후 "Finish"를 누른다.



5. pom.xml 수정하기


수정할 부분이 조금 많다.

어려운 건 아니니깐 따라하면 된다.


http://mvnrepository.com에 들어가서 정보를 잘 찾아서 변경해주면 된다.


변경하거나 추가한 부분은 굵은 글자로 표기하였다.


<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>com.web</groupId>

<artifactId>springsecurity5</artifactId>

<name>SpringSecurity5</name>

<packaging>war</packaging>

<version>1.0.0-BUILD-SNAPSHOT</version>

<properties>

<java-version>14</java-version>

<org.springframework-version>5.2.9.RELEASE</org.springframework-version>

<org.aspectj-version>1.6.10</org.aspectj-version>

<org.slf4j-version>1.6.6</org.slf4j-version>

</properties>

<dependencies>

<!-- Spring -->

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->

<dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-context</artifactId>

    <version>5.2.9.RELEASE</version>

<exclusions>

<!-- Exclude Commons Logging in favor of SLF4j -->

<exclusion>

<groupId>commons-logging</groupId>

<artifactId>commons-logging</artifactId>

</exclusion>

</exclusions>

</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->

<dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-webmvc</artifactId>

    <version>5.2.9.RELEASE</version>

</dependency>


<!-- AspectJ -->

<dependency>

<groupId>org.aspectj</groupId>

<artifactId>aspectjrt</artifactId>

<version>${org.aspectj-version}</version>

</dependency>

<!-- Logging -->

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-api</artifactId>

<version>${org.slf4j-version}</version>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>jcl-over-slf4j</artifactId>

<version>${org.slf4j-version}</version>

<scope>runtime</scope>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-log4j12</artifactId>

<version>${org.slf4j-version}</version>

<scope>runtime</scope>

</dependency>

<dependency>

<groupId>log4j</groupId>

<artifactId>log4j</artifactId>

<version>1.2.15</version>

<exclusions>

<exclusion>

<groupId>javax.mail</groupId>

<artifactId>mail</artifactId>

</exclusion>

<exclusion>

<groupId>javax.jms</groupId>

<artifactId>jms</artifactId>

</exclusion>

<exclusion>

<groupId>com.sun.jdmk</groupId>

<artifactId>jmxtools</artifactId>

</exclusion>

<exclusion>

<groupId>com.sun.jmx</groupId>

<artifactId>jmxri</artifactId>

</exclusion>

</exclusions>

<scope>runtime</scope>

</dependency>


<!-- @Inject -->

<dependency>

<groupId>javax.inject</groupId>

<artifactId>javax.inject</artifactId>

<version>1</version>

</dependency>


<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->

<dependency>

    <groupId>javax.servlet</groupId>

    <artifactId>javax.servlet-api</artifactId>

    <version>4.0.1</version>

    <scope>provided</scope>

</dependency>


<dependency>

<groupId>javax.servlet.jsp</groupId>

<artifactId>jsp-api</artifactId>

<version>2.1</version>

<scope>provided</scope>

</dependency>

<dependency>

<groupId>javax.servlet</groupId>

<artifactId>jstl</artifactId>

<version>1.2</version>

</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->

<dependency>

    <groupId>org.springframework.security</groupId>

    <artifactId>spring-security-core</artifactId>

    <version>5.4.0</version>

</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->

<dependency>

    <groupId>org.springframework.security</groupId>

    <artifactId>spring-security-web</artifactId>

    <version>5.4.0</version>

</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->

<dependency>

    <groupId>org.springframework.security</groupId>

    <artifactId>spring-security-config</artifactId>

    <version>5.4.0</version>

</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-taglibs -->

<dependency>

    <groupId>org.springframework.security</groupId>

    <artifactId>spring-security-taglibs</artifactId>

    <version>5.4.0</version>

</dependency>

<!-- Test -->

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>4.7</version>

<scope>test</scope>

</dependency>

<!-- Oracle DB(19 Version) -->

<dependency>

<groupId>com.oracle.database.jdbc</groupId>

<artifactId>ojdbc8</artifactId>

<version>19.3.0.0</version>

</dependency>

</dependencies>

    <build>

        <plugins>

            <plugin>

                <artifactId>maven-eclipse-plugin</artifactId>

                <version>2.9</version>

                <configuration>

                    <additionalProjectnatures>

                        <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>

                    </additionalProjectnatures>

                    <additionalBuildcommands>

                        <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>

                    </additionalBuildcommands>

                    <downloadSources>true</downloadSources>

                    <downloadJavadocs>true</downloadJavadocs>

                </configuration>

            </plugin>

            <plugin>

                <groupId>org.apache.maven.plugins</groupId>

                <artifactId>maven-compiler-plugin</artifactId>

                <version>2.5.1</version>

                <configuration>

                    <source>1.6</source>

                    <target>1.6</target>

                    <compilerArgument>-Xlint:all</compilerArgument>

                    <showWarnings>true</showWarnings>

                    <showDeprecation>true</showDeprecation>

                </configuration>

            </plugin>

            <plugin>

                <groupId>org.codehaus.mojo</groupId>

                <artifactId>exec-maven-plugin</artifactId>

                <version>1.2.1</version>

                <configuration>

                    <mainClass>org.test.int1.Main</mainClass>

                </configuration>

            </plugin>

        </plugins>

    </build>

</project>



파일명: pom.xml


[첨부(Attachments)]

pom.zip



6. 자바 버전 바꾸기 - Build Path, Project Facts


자바 버전을 변경해줘야 한다.



그림 19. Spring의 Properties 속성 메뉴


프로젝트를 클릭한다.

마우스 오른쪽 버튼을 누른다.

"Properties"를 클릭한다.



그림 20. Properties의 Build Path


JRE System Library [JavaSE-버전]을 14로 변경해준다. (1.7 이상으로만 해주면 됨.)

단, 버전은 Project Factes, Build Path, Pom.xml를 깔맞춤해주는 게 좋다.



그림 21. Properties의 Project Factes


Project Factes의 버전을 14로 변경해준다.




7. security-context.xml 파일 - 새로 만들기


"security-context.xml"은 초기에 파일이 존재한 건 아니다.

만들어줘야 한다.


폴더: src/main/java/webapp/WEB-INF/spring/

파일명: security-context.xml



그림 22. spring 폴더를 오른쪽 버튼 클릭했을 때 팝업 메뉴


spring 폴더를 오른쪽 버튼으로 클릭한다.

New->File을 클릭한다.



그림 23. xml 파일 생성하기


security-context.xml을 입력한 후, Finish를 누른다.



그림 24. xml 파일 생성하기





8. security-context.xml 파일 - 코드 편집(입력)


코드를 입력해준다.


<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"

    xmlns:beans="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/security 

http://www.springframework.org/schema/security/spring-security.xsd

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">

<!-- 보안 미적용 -->

<!-- <http pattern="/**/*.js" security="none" />  -->

<http auto-config="true" use-expressions="true">

      

<intercept-url pattern="/member/accessDenied" access="permitAll" />

<intercept-url pattern="/member/accessDeniedView" access="permitAll" />

<intercept-url pattern="/member/loginForm" access="permitAll" />

<intercept-url pattern="/" access="permitAll" />

<intercept-url pattern="/encode-password" access="permitAll" />

<intercept-url pattern="/admin/**" access="hasRole('ADMIN')" />

<intercept-url pattern="/**" access="hasAnyRole('USER')" />

<form-login login-page="/member/loginForm"

default-target-url="/"

authentication-failure-url="/member/loginForm?error"

username-parameter="id"

password-parameter="password" />

<!-- Form-login의 항목 -->

<!-- Default-Target-URL: 로그인 성공할 경우, 접속할 사이트 -->

<logout logout-url="/logout" logout-success-url="/" />

<!-- Servlet 3.0부터 access-denied-handler 미지원 -->

<!-- web.xml으로 제어할 것 -->

<!-- <access-denied-handler ref="customAccessDeniedHandler"/> -->

<access-denied-handler error-page="/member/accessDenied" />

<csrf disabled="true" />

</http>

<!-- DataSource 추후 지원 -->

<!-- 1. HSQLDB -->

<!-- ClassDriver = org.hsqldb.jdbcDriver -->

<!-- Url = jdbc:hsqldb:hsql://localhost:9001 -->

<!-- 2. Oracle JDBC -->

<!-- ClassDriver = oracle.jdbc.driver.OracleDriver -->

<!-- Url = jdbc:oracle:thin:@127.0.0.1:1521:orcl -->

<beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<beans:property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />

<beans:property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:orcl" />

<beans:property name="username" value="{사용자계정}" />

<beans:property name="password" value="{비밀번호}" />

</beans:bean>

<!-- 사용자 세부 계정 서비스 -->

<beans:bean id="userDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">

<beans:property name="dataSource" ref="dataSource" />

<beans:property name="usersByUsernameQuery" value="select username,password,enabled from comp_users where username = ?" />

<beans:property name="authoritiesByUsernameQuery" value="select username,authority from comp_authorities where username = ?" />

<beans:property name="groupAuthoritiesByUsernameQuery" value="select g.id, g.group_name, ga.authority from comp_groups g, 

  comp_group_members gm, comp_group_authorities ga where gm.username = ? 

  and g.id = ga.group_id and g.id = gm.group_id" />

</beans:bean>

<!-- provider -->

<authentication-manager>

<authentication-provider user-service-ref="userDetailsService">

<!-- <authentication-provider> -->

<!-- <user-service>-->

<!-- <user name="user" password="password" authorities="ROLE_USER" /> -->

<!-- xml 내에 사용자 계정 등록 -->

<!-- <user name="user" password="$2a$10$Gkr61IXH0YI/.Yh5T6fzteGLCLT6nOmMkID/DmFhWtPmu1WwPrDKq" authorities="ROLE_USER" /> -->

<!-- <user name="admin" password="password" authorities="ROLE_ADMIN" /> -->

<!-- </user-service> -->

<password-encoder ref="passwordEncoder"/>

</authentication-provider>

</authentication-manager>

<!-- 암호화 패키지 -->

<beans:bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" /> 


</beans:beans>


파일명: security-context.xml


[첨부(Attachments)]

security-context.zip



[순정 버전]


<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"

    xmlns:beans="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/security 

http://www.springframework.org/schema/security/spring-security.xsd

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">

<http>


<intercept-url pattern="/member/accessDenied" access="permitAll" />

<intercept-url pattern="/member/accessDeniedView" access="permitAll" />

<intercept-url pattern="/member/loginForm" access="permitAll" />

<intercept-url pattern="/" access="permitAll" />

<intercept-url pattern="/encode-password" access="permitAll" />

<intercept-url pattern="/admin/**" access="hasRole('ADMIN')" />

<intercept-url pattern="/**" access="hasAnyRole('USER')" />

<form-login login-page="/member/loginForm"

default-target-url="/"

authentication-failure-url="/member/loginForm?error"

username-parameter="id"

password-parameter="password" />

<!-- Form-login의 항목 -->

<!-- Default-Target-URL: 로그인 성공할 경우, 접속할 사이트 -->

<logout logout-url="/logout" logout-success-url="/" />

<access-denied-handler error-page="/member/accessDenied" />

</http> 


      

<authentication-manager>

<authentication-provider>

<user-service>

                              <user name="user" password="password" authorities="ROLE_USER" />

      <user name="admin" password="password" authorities="ROLE_ADMIN" />

</user-service>

</authentication-provider>

</authentication-manager>

</beans:beans>




물론 순정버전으로 구현해봐도 동작은 할 것이다. 실험해보려면 해봐도 무방하다.

최신 Spring Security 5의 경우에는 비밀번호 암호화에 대해서 엄격하게 확인을 하고 있다.
- 로그인 페이지는 열리는데, 암호화 패키지 문제 등이 발생할 수도 있다.


해결 방법1) 암호화 미적용이라고 지정해주기


<user name="user" password="{noop}password" authorities="ROLE_USER" />

<user name="admin" password="{noop}password" authorities="ROLE_ADMIN" />


{noop}를 넣어주면서 해결한다.


해결 방법2) 암호화 미적용이라고 지정해주기

(1단계: 비밀번호를 "
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" 클래스를 활용해서 인코딩한다.)


(2단계: 메모한 복잡한 암호를 password에 입력해준다.)


= <user name="user" password="$2a$10$Gkr61IXH0YI/.Yh5T6fzteGLCLT6nOmMkID/DmFhWtPmu1WwPrDKq" authorities="ROLE_USER" />


(3단계: 암호화 패키지를 사용하고 있다는 것을 정의해준다.)


                       (중략)

            <authentication-provider>

                       (중략)

<password-encoder ref="passwordEncoder"/>

</authentication-provider>


            <!-- 암호화 패키지 -->

           <beans:bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" /> 


</beans:beans>


"org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" 클래스는 Controller나 jsp 등에서도 계속 활용할 수 있다.




9. web.xml 파일 - 코드 편집


서블릿 버전이 낮게 정의된 web.xml 파일의 코드와 더불어 security-context.xml을 사용하고 있다는 것을 정의해줘야 한다.


<?xml version="1.0" encoding="UTF-8"?>

<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

  <filter>

    <filter-name>springSecurityFilterChain</filter-name>

    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

  </filter>

  <filter-mapping>

    <filter-name>springSecurityFilterChain</filter-name>

    <url-pattern>/*</url-pattern>

  </filter-mapping>

  <context-param>

    <param-name>contextConfigLocation</param-name>

    <param-value>

/WEB-INF/spring/root-context.xml

/WEB-INF/spring/security-context.xml

</param-value>

  </context-param>

  <listener>

    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

  </listener>

  <servlet>

    <servlet-name>appServlet</servlet-name>

    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

    <init-param>

      <param-name>contextConfigLocation</param-name>

      <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>

    </init-param>

    <load-on-startup>1</load-on-startup>

  </servlet>

  <servlet-mapping>

    <servlet-name>appServlet</servlet-name>

    <url-pattern>/</url-pattern>

  </servlet-mapping>

  <!-- <welcome-file-list>  -->

  <!--  <welcome-file>index.do</welcome-file> -->

  <!-- </welcome-file-list>    -->

  

  

  <!-- <error-page> -->

  <!--  <error-code>500</error-code> -->

  <!--  <location>/member/accessDenied</location> -->

  <!-- </error-page> -->

  

  <!-- <location>/WEB-INF/views/common/accessDenied.jsp</location> -->

</web-app>


파일명: web.xml


[첨부(Attachments)]

web.zip


(중략)

<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">


(중략)


변경 이력1: servlet 2.x -> 3.1 스펙으로 변경


(중략)


<filter>

    <filter-name>springSecurityFilterChain</filter-name>

    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

  </filter>

  <filter-mapping>

    <filter-name>springSecurityFilterChain</filter-name>

    <url-pattern>/*</url-pattern>

  </filter-mapping>

  <context-param>

    <param-name>contextConfigLocation</param-name>

    <param-value>

/WEB-INF/spring/root-context.xml

/WEB-INF/spring/security-context.xml

</param-value>

  </context-param>


(중략)


변경 이력2: springSecurityFillter와 security-context.xml 정의하기



10. Controller - CryptController.java


암호화 패키지에 관한 것이다. 기본 코드를 잘 활용하여 재사용해봐도 좋을 듯 싶다.


package com.web.springsecurity5.controller;


import java.text.DateFormat;

import java.util.Date;

import java.util.Locale;


import javax.servlet.http.HttpServletRequest;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.security.crypto.password.PasswordEncoder;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.context.WebApplicationContext;

import org.springframework.web.context.support.WebApplicationContextUtils;


@Controller

public class CryptController {


private static final Logger logger = LoggerFactory.getLogger(CryptController.class);

/**

* Simply selects the home view to render by returning its name.

*/

@RequestMapping(value = "/encode-password", method = RequestMethod.GET)

public String bcrypt(Locale locale, Model model, HttpServletRequest req) {

logger.info("Welcome home! The client locale is {}.", locale);

WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(req.getServletContext());

        PasswordEncoder passwordEncoder = context.getBean(PasswordEncoder.class);


        String password = req.getParameter("password");

        String encode = passwordEncoder.encode(password);


        model.addAttribute("encode", encode);

        System.out.println(encode);

        

return "home";

}

}



파일명: CryptController.java


[첨부(Attachments)]

CryptController.zip



* 2부에서 만나요.


2부에서 추가로 소개하도록 하겠다.


1. [Spring-Framework] 13. Spring MVC, Spring Security 5.4, Oracle - 보안처리(로그인-XML) (2), 2020-09-26

https://yyman.tistory.com/1420


반응형

+ Recent posts