SecurityConfig.java


package com.chatting.config;

import com.chatting.common.Constants;
import com.chatting.common.Url;
import com.chatting.service.CustomUsersDetailService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
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 javax.sql.DataSource;

/**
 * Security 설정 클래스
 */
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final LoginSucessHandler loginSucessHandler;
    private final LoginFailureHandler loginFailureHandler;
    private final DataSource dataSource;
    private final CustomUsersDetailService customUsersDetailService;

    /* configure */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(new FrontAuthenticationProvider());
        super.configure(auth);
    }

    /**
     * http 요청검사
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
            .rememberMe()
                .rememberMeParameter("remember-me")
                .userDetailsService(customUsersDetailService)
                .tokenRepository(tokenRepository()) //DataSource 추가

                .and()
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/file-download/**").permitAll()            //파일 다운로드
                .antMatchers("/auth/**").permitAll()					    //로그인, 회원가입 접속허용
                .antMatchers("/resource/**/images/**").permitAll()		//이미지
                .anyRequest().authenticated()
                .and()

                //로그인 화면 설정
            .formLogin()
                .permitAll()
                .loginPage(Url.AUTH.LOGIN)
                .loginProcessingUrl(Url.AUTH.LOGIN_PROC)
                .successHandler(loginSucessHandler)
                .failureHandler(loginFailureHandler)
                .usernameParameter(USERNAME_PARAM)
                .passwordParameter(PASSWORD_PARAM)
                .and()
            .logout()
                .logoutUrl(Url.AUTH.LOGOUT_PROC)
                .invalidateHttpSession(true)
                .deleteCookies("JSESSIONID","remember-me")//로그아웃 후 쿠키 삭제
                .and()

                //세션관리
                .sessionManagement()
                .maximumSessions(200) 				//세션 허용 갯수
                .expiredUrl(Url.AUTH.LOGIN)		 	//세션 만료시 이동할 페이지
                .sessionRegistry(sesionRegistry())
                .maxSessionsPreventsLogin(true);	//동시 로그인 차단, false인 경우 기존 세션 만료


    }

    @Override
    public void configure(WebSecurity web) throws Exception {

        web
                .ignoring()
                .antMatchers(Constants.STATIC_RESOURCES_URL_PATTERS)
                .antMatchers(HttpMethod.GET, "/exception/**");
        super.configure(web);
    }

    /**
     * 패스워드 암호화
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SessionRegistry sesionRegistry() {
        return new SpringSecuritySessionRegistImpl();
    }

    @Bean
    public PersistentTokenRepository tokenRepository() {
        // JDBC 기반의 tokenRepository 구현체
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource); // dataSource 주입
        return jdbcTokenRepository;
    }

    /* 관리자 아이디 파라미터 이름 */
    public static final String USERNAME_PARAM = "userId";

    /* 관리자 비밀번호 파라미터 이름 */
    public static final String PASSWORD_PARAM = "password";
}

 

 

customDetailService.java


package com.chatting.service;

import com.chatting.dto.QuickGuideUser;
import com.chatting.entity.Users;
import com.chatting.entity.UsersAuthority;
import com.chatting.repository.UsersAuthorityRepository;
import com.chatting.repository.UsersRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Slf4j
@RequiredArgsConstructor
@Service
public class CustomUsersDetailService implements UserDetailsService{

    private final UsersRepository usersRepository;
    private final UsersAuthorityRepository usersAuthorityRepository;

    @Override
    public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {

        Users user = usersRepository.findByUserId(userId);

        if (user == null){
            throw new UsernameNotFoundException(userId + "is not found. ");
        }
        QuickGuideUser quickGuideUser = new QuickGuideUser();
        quickGuideUser.setUsername(user.getUsername());
        quickGuideUser.setPassword(user.getPassword());
        quickGuideUser.setAuthorities(getAuthorities(userId));
        quickGuideUser.setEnabled(true);
        quickGuideUser.setAccountNonExpired(true);
        quickGuideUser.setAccountNonLocked(true);
        quickGuideUser.setCredentialsNonExpired(true);

        return quickGuideUser;
    }

    public Collection<GrantedAuthority> getAuthorities(String username) {
        List<UsersAuthority> authList = usersAuthorityRepository.findByUserId(username);
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (UsersAuthority authority : authList) {
            authorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
        }
        return authorities;
    }

}

 

QuickGuideUser.java


package com.chatting.dto;

import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

@Data
public class QuickGuideUser implements UserDetails {

    private String username;
    private String password;
    private boolean isEnabled;
    private boolean isAccountNonExpired;
    private boolean isAccountNonLocked;
    private boolean isCredentialsNonExpired;
    private Collection<? extends GrantedAuthority> authorities;

}

 

UsersRepository.java


package com.chatting.repository;

import com.chatting.dto.UsersDto;
import com.chatting.entity.Users;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UsersRepository extends CrudRepository<Users, Long> {

    //이름으로 찾기
    Users findByUserId(String userId);

}

 

UsersAuthorityRepository.java


package com.chatting.repository;

import com.chatting.dto.UsersAuthorityDto;
import com.chatting.entity.Users;
import com.chatting.entity.UsersAuthority;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface UsersAuthorityRepository extends CrudRepository<UsersAuthority, Long> {

    //아이디로 검색
    List<UsersAuthority> findByUserId(String userId);

}

 

Users.java


package com.chatting.entity;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import java.util.Collection;
import java.util.List;

@Getter
@Setter
@Entity
public class Users implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long userIdx;

    //사용자 아이디
    private String userId;

    //사용자 패스워드
    private String password;

    //사용자 핸드폰번호
    private String handPhoneNo;

    private String nickName;

    //사용여부
    private String useYn;

    //푸시토큰
    private String token;

    public Users() {}

    @Transient
    public List<UsersAuthority> authorities;

    @Builder
    public Users(String userId, String password, String handPhoneNo, String nickName, String useYn, String token) {
        this.userId = userId;
        this.password = password;
        this.handPhoneNo = handPhoneNo;
        this.nickName = nickName;
        this.useYn = useYn;
        this.token = token;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getUsername() {
        return userId;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

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

    @Override
    public boolean isEnabled() {
        return true;
    }
}

 

UsersAuthority.java


package com.chatting.entity;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import javax.persistence.*;

@Entity
@Getter
@Setter
public class UsersAuthority implements GrantedAuthority{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long authIdx;

    //사용자 id
    @Column(columnDefinition = "varchar(45) not null comment '사용자 아이디'")
    private String userId;

    //사용자 권한
    @Column(columnDefinition = "varchar(45) not null comment '권한'")
    private String authority;

    public UsersAuthority() {}

    @Builder
    public UsersAuthority(String userId, String authority) {
        this.userId = userId;
        this.authority = authority;
    }

}

 

PersistentLogins.java


package com.chatting.entity;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.time.LocalDateTime;

@Table(name = "persistent_logins")
@Entity
@Getter
@Setter
public class PersistentLogins {

    @Id
    @Column(length = 64)
    private String series;

    @Column(nullable = false, length = 64)
    private String username;

    @Column(nullable = false, length = 64)
    private String token;

    @Column(name = "last_used", nullable = false, length = 64)
    private LocalDateTime lastUsed;

}

 

login.html


<div class="form-group form-check">
    <input type="checkbox" class="form-check-input" id="rememberMe" name="remember-me" checked>
    <label class="form-check-label" for="rememberMe" aria-describedby="rememberMeHelp">로그인 유지</label>
</div>

쿠키에 remember-me라는 항목이 생긴것을 볼수있습니다.

 

persistent_logins 테이블에 로그인 쿠키 데이터가 저장된것을 볼수 있습니다.

 

 

복사했습니다!