미리 AWS 환경은 세팅해둔 상태! (external alb - web(ec2) - was(ec2))

직접 apache와 tomcat을 올려보기 위한 작업을 할 것이다...

 

왜 web - was 분리 구성을 하는가?

https://hwanchang.tistory.com/5


WEB 서버 구성(Apache)

  • Apache 설치 및 시작
yum install -y httpd
systemctl start httpd
  • alb 의 target group 상태검사 경로 변경 

  • index.html 생성
cd /var/www/html
vi index.html

# index.html
<html>
<body>WEB-A 서버입니다.</body>
</html>
  • health check: alb dns 주소를 통해 확인

  • health check: curl 명령어 활용

 

WAS 서버 구성(Tomcat)

  • java 다운로드 & tomcat 다운로드

https://www.oracle.com/java/technologies/downloads/

https://tomcat.apache.org/에서 다운로드

tar xvzf <파일명>
cp -R <디렉토리명> /usr/local/src
  • tomcat, jdk 경로 설정
vi /etc/profile

# 경로 확인
source /etc/profile
echo $JAVA_HOME
echo $CATALINA_HOME

  • tomcat 서비스 등록
cd /etc/init.d
vi tomcat

# tomcat file
#!/bin/bash
# Startup script for the Tomcat Server
# chkconfig : 345 50 50
# description: Tomcat is a Web application server.
# processname : java
# directory :
CATALINA_HOME=/usr/local/src/apache-tomcat-10.1.11
export JAVA_HOME=/usr/local/src/jdk-17.0.8
export CATALINA_HOME=/usr/local/src/apache-tomcat-10.1.11
case "$1" in
start)
echo "Starting tomcat: "
$CATALINA_HOME/bin/startup.sh
;;
stop)
echo "Shutting down tomcat: "
$CATALINA_HOME/bin/shutdown.sh
;;
restart)
echo "Restarting tomcat: "
$CATALINA_HOME/bin/shutdown.sh
$CATALINA_HOME/bin/startup.sh
;;
*)
echo "Usage: service tomcat"
start|stop|restart
exit 1
esac
exit 0

chmod 755 tomcat
service tomcat start

  • EC2 SG에 8080 포트 오픈
  • WAS 서버 index.html 생성
cd /usr/local/src/apache-tomcat-10.1.11/webapps/ROOT
vi index.html

# index.html
<html>
<body>WAS-A 서버입니다.</body>
</html>

 

WEB 서버 - WAS 서버 연동

  • WEB 서버 내부에 httpd.conf 파일 수정
vi /etc/httpd/conf/httpd.conf

###New Configuration
<VirtualHost *:80>
    ServerName <alb-dns주소>

    ProxyPass / http://<was-ip>:8080/
    ProxyPassReverse / http://<was-ip>:8080/
</VirtualHost>

systemctl restart httpd.service
  • WAS 서버
cd /usr/local/src/tomcat/bin
./shutdown.sh
./startup.sh
  • alb dns 접속 시 WAS 화면이 뜨면 성공 

 

 

참고: https://sweetysnail1011.tistory.com/57

 

❗java 다운받을 때 프로세서 확인 꼭 하기 uname -i 명령어 활용❗

'Computer Science > DevOps' 카테고리의 다른 글

[Jenkins] 다양한 플러그인 활용  (0) 2023.07.24
[Jenkins] Jenkins Pipeline 구축  (0) 2023.07.03
[Jenkins] Jenkins 다뤄보기  (0) 2023.06.27
[OS] OS 모음집  (0) 2021.08.12
[CI/CD] Tool  (0) 2021.08.11

내가 설정해둔 spring의 암호화 방법은 bCrypt이다.

 //MvcConfig
 @Bean(name = "bCryptPasswordEncoder")
  public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

 

초기 admin 계정을 넣을 때 비밀번호를 어떻게 찾을까?

https://www.devglan.com/online-tools/bcrypt-hash-generator

위의 사이트에서 text를 넣고 rounds를 선택해 Hash를 누르면 저절로 암호화된다.

이 암호문을 data.sql에 알맞은 위치에 넣고 실행시키면 admin 계정 비밀번호가 잘 저장되어 있을 것이다.

 

더 자세한 내용은

https://jusths.tistory.com/158

 

비밀번호 안전보관: bcrypt 를 알아보자

개요 사용자의 비밀번호를 그대로 보관하는 것은 위험하다. 비밀번호 보관에 특화된 bcrypt 를 알아보자. 참고링크 - 링크: https://auth0.com/blog/hashing-in-action-understanding-bcrypt/ - 링크: https://d2...

jusths.tistory.com

 

Intelli J에서 실행은 되지만 크롬 브라우저에서 들어가려고 하니.. 오류가 뜬다!

 

해결방법!

  • 주소를 https:// 에서 http://로 바꿔서 접속한다

 

❗ 너무 간단해서 오히려 화가난다.. ❗

❗ http와 https 사용법도 나중에 정리해야지.. ❗

'Web > Spring' 카테고리의 다른 글

[bcrypt] password 암호화  (0) 2021.08.10
[DB] 초기 데이터 삽입  (0) 2021.08.10
[Login구현] 권한부여(Role)  (0) 2021.08.10
[Login구현] 사용자 등록하기(Register)  (0) 2021.08.10
[Login구현] login-logout구현(setting)  (0) 2021.08.10

Role 사용을 해보자

내 프로젝트의 경우 ADD버튼이 admin으로 로그인했을 때만 나타나야 한다.

 

  • Role
@Getter
@AllArgsConstructor
public enum Role {
  ROLE_ADMIN("관리자", RoleBase.ADMINISTRATOR),
  ROLE_USER("사용자", RoleBase.USER);

  private String text;
  private RoleBase roleBase;

  public static List<Role> findByRoleBase(RoleBase roleBase) {
    return Arrays.stream(Role.values()).filter(p -> p.getRoleBase().equals(roleBase))
        .collect(Collectors.toList());
  }
}

일단 role은 ROLE_ADMIN과 ROLE_USER로 설정

DB에 admin 계정을 하나 넣어준다.(이를 처음 실행할 때 자동으로 넣어주도록 data.sql 파일을 생성해 넣어줘야 하는데 아직 구현하지 않았다.. 현재는 수동으로..!)

뒤에 User에 맞춰서 수동으로 넣어준다.

 

  • add버튼 관련
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<!--      admin-->
      <a class="addButton" th:text="|ADD|" th:href="|@{/project/edit}|" sec:authorize="hasRole('ROLE_ADMIN')"></a>

저렇게 설정하면 admin계정으로 로그인하면 add버튼이 보이고 logout 하면 보이지 않는다.

 

❗ 추가적으로 아예 관리자 대시보드를 만들려면 Controller에 어노테이션을 넣어준다. ❗

@Secured("ROLE_ADMIN")

그러면 해당 controller가 작동할 때는 admin계정으로 로그인했을 때이다.

login 구현

화면부터 구성해보자

  • /resources/templates/auth/login
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>login</title>
</head>
<body>
<form method="post" th:action="@{/auth/login/process}">
  <div style="padding: 30px"></div>
  <div style="padding: 15px">
    <input placeholder="username" id="username" name="username" type="text"/>
    <input placeholder="password" id="password" name="password" type="password"/>
    <button type="submit">login</button>
    <!--    로그인 실패 경고-->
    <div th:if="${error}" class="alert bg--error">
      <div class="alert__body">
        <span th:text="${error}" class="text-danger" style="color: #ff1034;"></span>
      </div>
    </div>
  </div>
  <div style="padding: 15px">
    <a th:href="@{/auth/find-account/username}">Forgot your Username or Password?</a>
    <span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
    <a th:href="@{/auth/register}">Sign up</a>
  </div>
</form>

</body>
</html>

 

  • AuthController : 화면과 연결
@Controller
@RequiredArgsConstructor
@RequestMapping("/auth")
public class AuthController {

  @NonNull
  private final UserService userService;
  @NonNull
  private final SmartValidator validator;

  @RequestMapping("/login")
  public String login() {
    return "auth/login";
  }
  }

 

  • UserService
@Service
@RequiredArgsConstructor
public class UserService implements UserDetailsService {

  @NonNull
  private final BCryptPasswordEncoder bCryptPasswordEncoder;
  @NonNull
  private final UserRepository userRepository;

  public Optional<User> findById(Integer id) {
    return userRepository.findById(id);
  }

  public Page<User> findAllByFilter(Pageable pageable, UserFilter filter) {
    return userRepository.findAllByFilter(pageable, filter);
  }

  public User save(User user) throws UserPasswordValidationException, DataNotFoundException {
    if (user.hasPasswordChanged()) {
      if (PasswordUtil.isValidPassword(user.getPassword())) {
        String encodedPassword = bCryptPasswordEncoder.encode(user.getPassword());
        user.setPassword(encodedPassword);
        user.setLastPasswordChangeDate(LocalDate.now());
      } else {
        throw new UserPasswordValidationException();
      }
    } else {
      user.setPassword(userRepository.findPasswordByUser(user));
    }
    return userRepository.save(user);
  }

  @Transactional
  public int removeByIdIn(List<Integer> ids) {
    return userRepository.removeByIdIn(ids);
  }

  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    Optional<User> user = userRepository.findByUsername(username);
    if (!user.isPresent()) {
      throw new UsernameNotFoundException("일치하는 사용자를 찾을 수 없습니다.");
    }
    return user.get();
  }

  public boolean existsByUsername(String username) {
    return userRepository.existsByUsername(username);
  }

  public boolean existsByName(String name) {
    return userRepository.existsByName(name);
  }

  public Page<User> findAllByIdIn(List<Integer> ids, Pageable pageable) {
    return userRepository.findAllByIdIn(ids, pageable);
  }
}

❗ 나중에 활용할 함수들 ❗

 

  • UserRepository
//UserRepository
@Repository
public interface UserRepository extends JpaRepository<User, Integer>, CustomUserRepository {

  Optional<User> findByUsername(String username);

  boolean existsByUsername(String username);

  boolean existsByName(String name);

  @Query("SELECT p.password FROM User p WHERE p.id = :#{#user.id}")
  String findPasswordByUser(@Param("user") User user);

  @Modifying
  @Query("UPDATE User p SET p.removed = true, p.username = CONCAT(p.username, '-removed') WHERE p.id IN(:ids)")
  int removeByIdIn(@Param("ids") List<Integer> ids);

  Page<User> findAllByIdIn(List<Integer> ids, Pageable pageable);
}

//CustomUserRepository
@NoRepositoryBean
public interface CustomUserRepository {

  Page<User> findAllByFilter(Pageable pageable, UserFilter filter);

}

//UserRepositoryImpl
public class UserRepositoryImpl extends QuerydslRepositorySupport implements CustomUserRepository {

  private QUser user = QUser.user;

  public UserRepositoryImpl() {
    super(User.class);
  }

  @Override
  public Page<User> findAllByFilter(Pageable pageable, UserFilter filter) {
    BooleanBuilder builder = new BooleanBuilder();

    builder.and(user.removed.isFalse());

    if (filter.getFRole() != null) {
      builder.and(user.role.eq(filter.getFRole()));
    }

    if (!StringUtils.isBlank(filter.getFName())) {
      builder.and(user.name.containsIgnoreCase(filter.getFName()));
    }

    if (!StringUtils.isBlank(filter.getFUsername())) {
      builder.and(user.username.containsIgnoreCase(filter.getFUsername()));
    }

    if (filter.getFLocked() != null) {
      builder.and(user.locked.eq(filter.getFLocked()));
    }

    final JPQLQuery<User> query = from(user).where(builder);
    final List<User> result = getQuerydsl().applyPagination(pageable, query).fetch();
    return new PageImpl<>(result, pageable, query.fetchCount());
  }
}

 

  • UserFilter
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserFilter extends QueryStringSupport {

  private Role fRole;

  private String fUsername;

  private String fName;

  private Boolean fLocked;

}

 

    • QueryStringSupport
@Getter
@Setter
public class QueryStringSupport {

  protected String sort;
  protected Integer page;

  public String getQueryString() {
    try {
      List<String> queries = new ArrayList<>();
      List<Field> fieldList = FieldUtils.getAllFieldsList(this.getClass());
      for (Field f : fieldList) {
        String key = f.getName();
        Object value = FieldUtils.readField(f, this, true);

        if (value == null) {
          // querys.add(key + "=");
          continue;
        }

        if (value instanceof Collection) {
          Collection col = (Collection) value;
          for (Object o : col) {
            queries.add(key + "=" + new URLCodec().encode(o.toString()));
          }
        } else {
          queries.add(key + "=" + new URLCodec().encode(value.toString()));
        }
      }

      return Strings.join(queries, '&');
    } catch (IllegalAccessException | EncoderException e) {
      return "";
    }
  }

  public void fromQueryString(String queryString) {
    List<Field> fieldList = FieldUtils.getAllFieldsList(this.getClass());
    List<NameValuePair> nameValuePairs = URLEncodedUtils
        .parse(queryString, Charset.forName("UTF-8"));

    for (Field f : fieldList) {
      String fieldName = f.getName();

      for (NameValuePair p : nameValuePairs) {
        String paramName = p.getName();

        if (fieldName.equalsIgnoreCase(paramName)) {
          try {
            FieldUtils.writeField(f, this, p.getValue());
          } catch (IllegalAccessException e) {
            //DO Nothing
          }
          break;
        }
      }
    }
  }
}

 

위와 같이 코드를 상황에 맞게 설정하고 DB에 사용자를 추가해서 login해보기!

 

 

logout구현

<html xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

<a sec:authorize="isAnonymous()" th:href="@{/auth/login}" style="padding: 15px">login</a>
<a sec:authorize="isAuthenticated()" th:href="@{/auth/logout}" style="padding: 15px">logout</a>

isAnonymous() : 익명의 사용자

isAuthenticated() : 인증한 사용자

 

위와 같이 설정하면 로그인 하기 전에는 login a태그를 로그인에 성공하고 나면 logout a태그를 보여준다.

 

❗ 다음 포스팅은 register 관련 ❗

login을 위한 setting

  • pom.xml
<!--    security-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>org.thymeleaf.extras</groupId>
      <artifactId>thymeleaf-extras-springsecurity5</artifactId>
    </dependency>

 

  • domain-User
@Getter
@Setter
@Entity
@Table(name = "user", indexes = {@Index(columnList = "username"), @Index(columnList = "name"),
    @Index(columnList = "role"), @Index(columnList = "enabled"), @Index(columnList = "removed")})
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class User implements UserDetails {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @EqualsAndHashCode.Include
  private Integer id;

  @Column(nullable = false, insertable = false, updatable = false, columnDefinition = "datetime default CURRENT_TIMESTAMP")
  @DateTimeFormat(pattern = DateUtil.PATTERN_YMDHM)
  private LocalDateTime createDate;   //생성일시

  @Size(min = 2, max = 32)
  @Column(unique = true, nullable = false, updatable = false)
  private String username;

  @Size(min = 2, max = 8)
  @Column(nullable = false)
  private String name;

  @Column(nullable = false)
  @JsonIgnore
  private String password;

  @Enumerated(EnumType.STRING)
  @Column(nullable = false, updatable = false, columnDefinition = "varchar(50)")
  private Role role;

  @JsonIgnore
  @DateTimeFormat(pattern = DateUtil.PATTERN_YMD)
  @Column(columnDefinition = "date default CURRENT_TIMESTAMP")
  private LocalDate lastPasswordChangeDate = LocalDate.now();

  // 화면에서 추가인지 수정인지 여부를 보여주기 위함
  @Transient
  @JsonIgnore
  private boolean saved = false;

  @PostLoad
  private void postLoad() {
    this.saved = true;
  }

  @DateTimeFormat(pattern = DateUtil.PATTERN_YMD)
  private LocalDate expireDate;   //계정 만료일(null은 만료없음)

  @JsonIgnore
  @Column(columnDefinition = "bit(1) default 1", nullable = false)
  private boolean enabled = true;

  @JsonIgnore
  @Column(columnDefinition = "bit(1) default 0", nullable = false)
  private boolean locked = false;

  @JsonIgnore
  @Column(columnDefinition = "bit(1) default 0", nullable = false, insertable = false, updatable = false)
  private boolean superUser = false;

  @JsonIgnore
  @Column(columnDefinition = "bit(1) default 0", nullable = false, insertable = false)
  private boolean removed = false;

  @Override
  public Collection<? extends GrantedAuthority> getAuthorities() {
    List<GrantedAuthority> authorities = new ArrayList<>(0);
    authorities.add(new SimpleGrantedAuthority(role.name()));
    return authorities;
  }

  /**
   * 신규 사용자이거나 비밀번호 란에 비밀번호를 입력한 경우 비밀번호 유효성 검사 필요
   */
  public boolean hasPasswordChanged() {
    try {
      return !password.isEmpty() || id == null;
    } catch (Exception e) {

    }
    return false;
  }

  @JsonIgnore
  @Override
  public boolean isCredentialsNonExpired() {
    return true;
  }

  @JsonIgnore
  @Override
  public boolean isAccountNonExpired() {
//    if (expireDate != null) {
//      return expireDate.isAfter(LocalDate.now());
//    }
    return true;
  }

  @JsonIgnore
  @Override
  public boolean isAccountNonLocked() {
    return !locked && !removed;
  }

  @JsonIgnore
  @Override
  public boolean isEnabled() {
    return enabled && !removed;
  }

//  @JsonIgnore
//  public boolean hasRole(Role role) {
//    return this.role.equals(role);
//  }
}

 

  • domain-Certification
@Getter
@Setter
@Entity
@Table(name = "certification", indexes = {@Index(columnList = "phone")})
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Certification {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @EqualsAndHashCode.Include
  private Integer id;

  @Size(min = 12, max = 13)
  @NotNull
  private String phone;

  @Size(min = 6, max = 6)
  @NotNull
  private String authNumber;

  @JsonIgnore
  @Column(columnDefinition = "bit(1) default 0", nullable = false)
  private boolean certified = false;

  @JsonFormat(pattern = DateUtil.PATTERN_YMDHM)
  @DateTimeFormat(pattern = DateUtil.PATTERN_YMD)
  private LocalDateTime expireDate = LocalDateTime.now().plusMinutes(3);   //계정 만료일(null은 만료없음)
}

 

  • enumerate
// RoleBase
@Getter
@AllArgsConstructor
public enum RoleBase {
  ADMINISTRATOR("관리자"),
  USER("사용자");

  private String text;
}

//Role
@Getter
@AllArgsConstructor
public enum Role {
  ROLE_ADMIN("관리자", RoleBase.ADMINISTRATOR),
  ROLE_USER("사용자", RoleBase.USER);

  private String text;
  private RoleBase roleBase;

  public static List<Role> findByRoleBase(RoleBase roleBase) {
    return Arrays.stream(Role.values()).filter(p -> p.getRoleBase().equals(roleBase))
        .collect(Collectors.toList());
  }

}

❗ role을 사용하는 이유는 admin을 위해서!! 권한을 부여해줄 예정 ❗

 

  • config-SecurityConfig
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  private static final int TOKEN_VALIDITY_TIME = 604800;

  @NonNull
  private final BCryptPasswordEncoder bCryptPasswordEncoder;
  @NonNull
  private final UserService userService;

  @Bean
  public FilterRegistrationBean<Filter> getSpringSecurityFilterChainBindedToError(
      @Qualifier("springSecurityFilterChain") Filter springSecurityFilterChain) {
    FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
    registration.setFilter(springSecurityFilterChain);
    registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
    return registration;
  }

  @Bean
  public AuthenticationFailureHandler customAuthenticationFailureHandler() {
    return new CustomAuthenticationHandler();
  }

  @Bean
  public AuthenticationSuccessHandler customAuthenticationSuccessHandler() {
    return new CustomAuthenticationHandler();
  }

  @Bean
  public LoginUrlAuthenticationEntryPoint ajaxAwareAuthenticationEntryPoint() {
    return new AjaxAwareAuthenticationEntryPoint(SpringSecurity.LOGIN_URL);
  }

  @Bean
  public AccessDeniedHandler customAccessDeniedHandler() {
    return new CustomAccessDeniedHandler();
  }

  /**
   * 유저 DB의 DataSource와 Query 및 Password Encoder 설정.
   */
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
  }

  /**
   * Spring Security에서 인증받지 않아도 되는 리소스 URL 패턴을 지정해 줍니다.
   */
  @Override
  public void configure(WebSecurity web) {
    web.ignoring().antMatchers("/static/**");
  }

  @Bean
  public CorsConfigurationSource corsConfigurationSource() {
    final CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Collections.singletonList("*"));
    configuration.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH"));
    // setAllowCredentials(true) is important, otherwise:
    // The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
    configuration.setAllowCredentials(true);
    // setAllowedHeaders is important! Without it, OPTIONS preflight request
    // will fail with 403 Invalid CORS request
    configuration.setAllowedHeaders(Collections.singletonList("Authorization"));
    final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
  }

  /**
   * Spring Security에 의해 인증받아야 할 URL 또는 패턴을 지정해 줍니다.
   */
  @Override
  protected void configure(HttpSecurity http) throws Exception {

    http.headers().frameOptions().sameOrigin()
        .and().csrf().disable().authorizeRequests()
        .antMatchers("/admin/**","/mypage/**","/comment/**").authenticated()
        .anyRequest().permitAll()
        .and().formLogin().loginPage(SpringSecurity.LOGIN_URL)
        .loginProcessingUrl(SpringSecurity.LOGIN_PROCESS_URL)
        .successHandler(customAuthenticationSuccessHandler())
        .failureHandler(customAuthenticationFailureHandler())
        .usernameParameter(SpringSecurity.PARAM_USERNAME)
        .passwordParameter(SpringSecurity.PARAM_PASSWORD)
        .and().logout()
        .logoutRequestMatcher(new AntPathRequestMatcher(SpringSecurity.LOGOUT_URL))
        .logoutSuccessUrl(SpringSecurity.LOGIN_URL)
        .and().exceptionHandling().authenticationEntryPoint(ajaxAwareAuthenticationEntryPoint())
        .accessDeniedHandler(customAccessDeniedHandler())
        .and().rememberMe().disable();
  }
}

 

  • config-MvcConfig
  @Bean(name = "bCryptPasswordEncoder")
  public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

❗ MvcConfig에 @Bean으로 추가를 해야 사용이 가능하다!! ❗

 

  • config-PasswordConfig
@Getter
@Setter
@Configuration
@ConfigurationProperties("password")
@PropertySources({@PropertySource(value = "classpath:/config/password.properties")})
public class PasswordConfig {

  private Validation validation;

  @Getter
  @Setter
  @Configuration
  @ConfigurationProperties("herbnet.password-validation")
  public static class Validation {

    private int length;
    private boolean specialCharacters;
    private boolean upperCases;
    private boolean numbers;
    private int maxAttemptsCount;
    private int changeCycle;
  }

}

❗ password.properties는 취향 것.. ❗

알아서 설정

  • config-기타
//AjaxAwareAuthenticationEntryPoint
public class AjaxAwareAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {

  public AjaxAwareAuthenticationEntryPoint(String loginUrl) {
    super(loginUrl);
  }

  @Override
  public void commence(HttpServletRequest request, HttpServletResponse response,
      AuthenticationException authException) throws IOException, ServletException {
    String ajaxHeader = request.getHeader("X-Requested-With");
    boolean isAjax = "XMLHttpRequest".equals(ajaxHeader);

    if (isAjax) {
      response
          .sendError(HttpServletResponse.SC_UNAUTHORIZED, "Ajax Request Denied (Session Expired)");
    } else {
      super.commence(request, response, authException);
    }
  }
}

//CustomAuthenticationHandler
public class CustomAuthenticationHandler
    implements AuthenticationSuccessHandler, AuthenticationFailureHandler {

  @Override
  public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
      Authentication auth) throws IOException {

    response.sendRedirect(request.getContextPath() + SpringSecurity.LOGIN_SUCCESS_URL);
  }

  @Override
  public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
      AuthenticationException exception) throws IOException, ServletException {

    String username = request.getParameter("username");
    String dispatcherURL = SpringSecurity.LOGIN_URL;

    request.setAttribute("SPRING_SECURITY_LAST_EXCEPTION", exception);
    request.setAttribute("SPRING_SECURITY_LAST_USERNAME", username);
    request.setAttribute("username", username);
    request.setAttribute("error", getErrorMessage(exception));
    request.getRequestDispatcher(dispatcherURL).forward(request, response);
  }

  /**
   * Spring Security 가 반환하는 에러 메시지 얻어옴
   *
   * @return 에러메시지
   */
  private String getErrorMessage(AuthenticationException exception) {

    String error;
    if (exception instanceof AccountExpiredException) {
      error = "사용 기간이 만료된 계정입니다.";
    } else if (exception instanceof BadCredentialsException) {
      error = "아이디 또는 비밀번호를 확인해주시기 바랍니다.";
    } else if (exception instanceof LockedException) {
      error = "로그인 반복 실패 또는 관리자에 의해 잠겨있는 계정입니다.\n관리자에게 문의하세요.";
    } else if (exception instanceof InternalAuthenticationServiceException) {
      error = "존재하지 않는 계정입니다.";
    } else if (exception instanceof DisabledException) {
      error = "승인되지 않은 계정입니다. 관리자의 승인을 기다려주세요.";
    } else if (exception instanceof CredentialsExpiredException) {
      error = "만료된 비밀번호 입니다.";
    } else {
      error = exception.getLocalizedMessage();
    }

    return error;
  }
}

//CustomAccessDeniedHandler
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

  @Override

  public void handle(HttpServletRequest request, HttpServletResponse response,
      AccessDeniedException accessDeniedException) throws IOException {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

    if (authentication instanceof AnonymousAuthenticationToken) {
      response.sendRedirect(request.getContextPath() + SpringSecurity.LOGIN_URL);
    }
  }
}

 

  • util-ApplicationContextProvider
public class ApplicationContextProvider implements ApplicationContextAware {

  private static ApplicationContext applicationContext;

  public static <T> T getEnvironmentProperty(String key, Class<T> targetClass, T defaultValue) {
    if (key == null || targetClass == null) {
      throw new NullPointerException();
    }

    T value = null;
    if (applicationContext != null) {
      value = applicationContext.getEnvironment().getProperty(key, targetClass, defaultValue);
    }
    return value;
  }

  public void setApplicationContext(@NonNull ApplicationContext applicationContext) {
    ApplicationContextProvider.applicationContext = applicationContext;
  }
}

 

  • util-LoginAuth
public class LoginAuth {

  /* 계정 정보 */
  public static final String PASSWORD_SPECIAL_REGEXP = "(?=.*[!@#$%^&*()\\-_=+\\\\\\|\\[\\]{};:\\'\",.<>\\/?])";
  public static final String PASSWORD_UPPER_REGEXP = "(?=.*[A-Z])";
  public static final String PASSWORD_NUMBER_REGEXP = "(?=.*\\d)";
}

 

  • util-PasswordUtil
@UtilityClass
public class PasswordUtil {

  public static boolean isValidPassword(String password) {
    int length = ApplicationContextProvider
        .getEnvironmentProperty("validation.length", Integer.class, 0);
    boolean specialCharacters = ApplicationContextProvider
        .getEnvironmentProperty("validation.special-characters", Boolean.class,
            false);
    boolean upperCases = ApplicationContextProvider
        .getEnvironmentProperty("validation.upper-cases", Boolean.class, false);
    boolean numbers = ApplicationContextProvider
        .getEnvironmentProperty("validation.numbers", Boolean.class, false);

    String baseRegExp = "";
    String specialRegExp = "";
    String upperRegExp = "";
    String numberRegExp = "";

    if (specialCharacters) {
      specialRegExp = LoginAuth.PASSWORD_SPECIAL_REGEXP;
    }
    if (upperCases) {
      upperRegExp = LoginAuth.PASSWORD_UPPER_REGEXP;
    }
    if (numbers) {
      numberRegExp = LoginAuth.PASSWORD_NUMBER_REGEXP;
    }

    baseRegExp =
        "^" + specialRegExp + upperRegExp + numberRegExp
            + "[A-Za-z\\d!@#$%^&*()\\-_=+\\\\\\|\\[\\]{};:\\'\",.<>\\/?]{" + length + ",}";

    if (!StringUtils.isBlank(password) && password.matches(baseRegExp)) {
      return true;
    }
    return false;
  }

  public static String getPasswordRuleMessage() {
    String message = "비밀번호는 ";
    List<String> ruleIncluded = new ArrayList<>();

    int length = ApplicationContextProvider
        .getEnvironmentProperty("validation.length", Integer.class, 0);
    boolean specialCharacters = ApplicationContextProvider
        .getEnvironmentProperty("validation.special-characters", Boolean.class,
            false);
    boolean upperCases = ApplicationContextProvider
        .getEnvironmentProperty("validation.upper-cases", Boolean.class, false);
    boolean numbers = ApplicationContextProvider
        .getEnvironmentProperty("validation.numbers", Boolean.class, false);

    ruleIncluded.add("영문 소문자");

    if (specialCharacters) {
      ruleIncluded.add("특수문자");
    }

    if (upperCases) {
      ruleIncluded.add("영문 대문자");
    }

    if (numbers) {
      ruleIncluded.add("숫자");
    }

    if (!ruleIncluded.isEmpty()) {
      message += String.format("%s를 포함하여 ", StringUtils.join(ruleIncluded, ", "));
    }

    message += String.format("%d자리 이상으로 구성되어야 합니다.", length);

    return message;
  }
}

 

  • util-SpringSecurity
    public class SpringSecurity {
      public static final String LOGIN_URL = "/auth/login";
      public static final String LOGIN_PROCESS_URL = "/auth/login/process";
      public static final String LOGIN_SUCCESS_URL = "/";
      public static final String LOGOUT_URL = "/auth/logout";
      public static final String PARAM_USERNAME = "username";
      public static final String PARAM_PASSWORD = "password";
      public static final String PARAM_REMEMBER = "remember-me";
      public static final String KEY_REMEMBER = "알아서";
    }​

 

  • exception-UserPasswordValidationException
@SuppressWarnings("serial")
public class UserPasswordValidationException extends Exception {

  @Override
  public String getMessage() {
    return "비밀번호 규칙이 일치하지 않습니다";
  }
}

 

  • exception-DataNotFoundException
@ResponseStatus(code = HttpStatus.NOT_FOUND)
public class DataNotFoundException extends HttpClientErrorException {

  public DataNotFoundException() {
    super(HttpStatus.NOT_FOUND, "데이터를 찾을 수 없습니다.");
  }

  public DataNotFoundException(String message) {
    super(HttpStatus.NOT_FOUND, message);
  }
}

 

 

+ Recent posts