스프링 시큐리티 - Authentication.

하쮸·2025년 1월 9일
0

1. 회원가입 구현.


1-1. 로그인 페이지.

IndexController

package com.cos.securityex01.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller     // view 반환.
public class IndexController {
    @GetMapping({"", "/"})
    public String index() {
        return "index";     // src/main/resources/templates/index.mustache
    }

			....

    @GetMapping("/login")           
    public String login() {
        return "loginForm";
    }

}
  • 기존 코드에서 login() 메서드@ResponseBody에노테이션을 제거하고, return "loginForm"으로 수정.

loginForm.html

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>로그인 페이지</title>
</head>
<body>
<h1>로그인 페이지</h1>
<hr/>
<form>
    <input type="text" name="username" placeholder="Username"/><br>
    <input type="password" name="password" placeholder="Password"/><br>
    <button>로그인</button>
</form>
</body>
</html>

  • 현재 인덱스 페이지는 SecurityConfig에서 .anyRequest().permitAll()에 해당돼서 바로 접속 가능함.

SecurityConfig

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf().disable();
        httpSecurity.authorizeRequests()
                .antMatchers("/user/**").authenticated()    // 해당 주소는 로그인이 필요.
                .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")      // 해당 주소로 접속 시 로그인뿐만 아니라 권한(admin, manager)도 필요함.
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")                                   // 해당 주소는 admin 권한만 들어갈 수 있음.
                .anyRequest().permitAll()      // 그외 다른 요청은 전부 허용.
                .and().formLogin().loginPage("/login");                       // 권한이 없는 주소로 요청을 하면 로그인 페이지로 이동되게 설정.
    }
  • /user는 인증이 되어있어야되고, /manager, /admin은 특정 권한이 있어야 됨.
    • 위 페이지로 접속 시 자동으로 로그인 페이지로 이동.

  • 로그인 페이지로 이동은 되지만 현재는 회원가입이 안 되어 있어서 로그인을 못함.
    • 즉 DB user테이블에 데이터가 없음.

1-2. User 클래스

  • model 패키지를 만들고 User 클래스 생성.

User

import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.sql.Timestamp;

@Entity
@Data
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String username;

    private String password;

    private String email;

    private String role;            // ROLE_USER, ROLE_ADMIN

    @CreationTimestamp
    private Timestamp createDate;
}
  • 서버 재실행하고 DB를 보면 user테이블이 만들어져 있음.

  • DESC user;로 칼럼 확인.

1-3. Controller, SecurityConfig 수정.

IndexController

    @GetMapping("/loginForm")           
    public String loginForm() {
        return "loginForm";
    }

    @GetMapping("/joinForm")
    public String joinForm() {
        return "joinForm";
    }

SecurityConfig

.and().formLogin().loginPage("/loginForm");                       // 권한이 없는 주소로 요청을 하면 로그인 페이지로 이동되게 설정.
  • loginPage()만 변경.

1-4. loginForm 템플릿 수정.

loginForm.html

<body>
<h1>로그인 페이지</h1>
<hr/>
<form>
    <input type="text" name="username" placeholder="Username"/><br>
    <input type="password" name="password" placeholder="Password"/><br>
    <button>로그인</button>
</form>
<a href="/joinForm">회원가입을 아직 하지 않으셨나요?</a>
</body>

IndexController

    @GetMapping("/joinForm")
    public String joinForm() {          // 회원가입 페이지.
        return "joinForm";
    }
  • 회원가입 버튼을 누르면 이 메서드가 처리할텐데 현재 joinForm이라는 템플릿이 없어서 404 에러가 발생함.


1-5. joinForm 템플릿 생성.

joinForm.html

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>회원가입 페이지</title>
</head>
<body>
<h1>회원가입 페이지</h1>
<hr/>
<form>
    <input type="text" name="username" placeholder="Username"/><br>
    <input type="password" name="password" placeholder="Password"/><br>
    <input type="email" name="email" placeholder="Email"/><br>
    <button>회원가입</button>
</form>
</body>
</html>

  • 회원가입을 진행할 예정이므로
    <form action="/join" method="POST">으로 수정.
<form action="/join" method="POST">
    <input type="text" name="username" placeholder="Username"/><br>
    <input type="password" name="password" placeholder="Password"/><br>
    <input type="email" name="email" placeholder="Email"/><br>
    <button>회원가입</button>
</form>

1-6. 회원가입 메서드.

IndexController

    @PostMapping("/join")
    @ResponseBody
    public String join(User user) {              // 회원가입
        System.out.println(user);
        return "join";
    }


  • 정상적으로 처리되어서 join문자열이 반환됐음.

  • System.out.println으로 찍은 콘솔도 정상적으로 값을 받아서 출력함.


1-7. Repository 생성.

UserRepository

public interface UserRepository extends JpaRepository<User, Integer> {
    
}
  • CRUD 메서드를 JpaRepository가 들고있음.
  • UserRepositoryJpaRepository상속 받았기 때문에 @Repository에노테이션이 없어도 IoC가 됨.
    • 즉, 자동으로 빈 등록 됨.

1-8. DI.

IndexController

    @Autowired
    private UserRepository userRepository;
  • @Autowired를 통해 DI(의존성 주입).

1-9. 회원가입 메서드 수정.

IndexController

    @PostMapping("/join")
    @ResponseBody
    public String join(User user) {              // 회원가입
        System.out.println(user);
        user.setRole("ROLE_USER");
        userRepository.save(user);
        return "join";
    }
  • setter를 이용해서 권한(Role) 값을 넣어줌.
  • 이렇게 하면 회원가입은 잘 됨.
    • But이렇게 하면 안됨.
    • 왜냐하면 비밀번호가 암호화가 안돼서 시큐리티로 로그인을 못함.

1-10. SecurityConfig 수정.

SecurityConfig

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
  • BCryptPasswordEncoder를 빈으로 등록.
  • 해당 메서드리턴되는 객체를 IoC등록해줌.

1-11. DI.

IndexController

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;
  • 아까와 마찬가지로 @Autowired를 통해 DI해줌.

1-12. 회원가입 메서드 수정.

IndexController

    @PostMapping("/join")
    public String join(User user) {              // 회원가입
        System.out.println(user);
        user.setRole("ROLE_USER");
        String rawPassword = user.getPassword();
        String encodePassword = bCryptPasswordEncoder.encode(rawPassword);
        user.setPassword(encodePassword);
        userRepository.save(user);
        return "redirect:/loginForm";
    }
  • 사용자로부터 받은 비밀번호를 암호화해서 저장.
  • 정상적으로 회원가입이 됐다면 loginForm으로 리다이렉트 시켜주기.

  • 회원가입이 정상적으로 돼서 loginForm으로 리다이렉트 됐음.

  • 콘솔에도 정상적으로 찍힘.

  • DB에서 SELECT로 조회도 잘 됨.

2. 로그인 구현.


2-1. SecurityConfig 수정.

SecurityConfig

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf().disable();
        httpSecurity.authorizeRequests()
                .antMatchers("/user/**").authenticated()    // 해당 주소는 로그인이 필요.
                .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")      // 해당 주소로 접속 시 로그인뿐만 아니라 권한(admin, manager)도 필요함.
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")                                   // 해당 주소는 admin 권한만 들어갈 수 있음.
                .anyRequest().permitAll()      // 그외 다른 요청은 전부 허용.
                .and().formLogin().loginPage("/loginForm")                       // 권한이 없는 주소로 요청을 하면 로그인 페이지로 이동되게 설정.
                .loginProcessingUrl("/login")                                      // '/login' 주소가 호출되면 시큐리티가 대신 로그인을 처리함. -> 그래서 컨트롤러에 login 메서드가 필요없음.
                .defaultSuccessUrl("/");                                            // 로그인 성공 시 메인 페이지로 이동.
    }
  • .loginProcessingUrl("/login")
    • /login이 호출되면 시큐리티가 대신 로그인을 처리함.
    • 따라서 컨트롤러에 /login을 처리하는 login 메서드가 필요없음.
  • .defaultSuccessUrl("/");
    • 로그인 성공 시 /(메인 페이지)로 이동.

2-2. loginForm 수정.

loginForm.html

<form action="/login" method="POST">
    <input type="text" name="username" placeholder="Username"/><br>
    <input type="password" name="password" placeholder="Password"/><br>
    <button>로그인</button>
</form>
  • username, password를 가지고 /login으로 요청을 하면 시큐리티가 대신 로그인을 처리.

2-3. PrincipalDetails 클래스 생성.

  • 시큐리티가 /login 주소로 요청이 들어오면 대신 로그인을 처리함.

    • 로그인 처리가 완료 되면 SecurityContext를 생성하고, 이 SecurityContext에 인증된 사용자의 Authentication 객체를 저장.
  • SecurityContext는 HTTP 세션에 저장되며, 이를 통해 사용자가 로그인 상태를 유지할 수 있음.

    • 즉, 인증이 완료되면 인증 객체가 생성되고 인증 객체Security Context에 저장되고, Security Context HodlerSecurity Context존재.
    • 스프링 시큐리티에서는 SecurityContext안에 Authentication 객체존재하는지의 유무를 체크해서 인증여부를 결정함.
    • 그렇기 때문에 SecurityContext는 사용자가 재접속하더라도 인증 당시데이터가 보존이 되어 있어야 함.
    • 사용자가 인증처리가 완료된 후 다시 사이트를 접속했을 때 세션에 저장된 SecurityContext 를 꺼내어 와서 SecurityContextHolder에 저장하게 되고
      이는 전역으로 SecurityContext참조할 수 있게 하는 원리가 됨.
  • Authentication 안에 User 정보가 있어야 됨.

    • 여기서 User 타입은 UserDetails 타입 객체.
      즉, Authentication 객체 안에 User정보는 UserDetails 타입 객체.

PrincipalDetails class

public class PrincipalDetails implements UserDetails {     

    private User user;      // 포함관계(합성(Composition))

    public PrincipalDetails(User user) {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {        
        Collection<GrantedAuthority> collection = new ArrayList<>();
        collection.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return user.getRole();
            }
        });
        return collection;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

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

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

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

    @Override
    public boolean isEnabled() {                    
        return true;                                
    }
}
  • UserDetails 인터페이스구현했으므로 PrincipalDetails 타입의 객체를 UserDetails 타입으로 다룰 수 있음.
    • 즉, PrincipalDetailsAuthentication안에 넣을 수 있음.
  • public Collection<? extends GrantedAuthority> getAuthorities()
    • 유저의 권한을 리턴하는 메서드.
    • 현재 유저의 권한은 user.getRole();이지만 반환 타입이 String임.
    • 해당 메서드의 반환 타입은 Collection<? extends GrantedAuthority>
  • isAccountNonExpired
    • 계정 만료 여부.
      (true : 만료되지 않음, false : 만료됐음.)
  • isAccountNonLocked
    • 계정 잠금 여부.
      (true : 잠겨있지 않음, false : 잠겨 있어서 접근 제한됨.)
  • isCredentialsNonExpired
    • 사용자의 자격 증명이 유효한지.
      (true : 유효함, false : 유효하지 않음.)
    • Ex) 일정 기간 동안 비밀번호를 변경하지 않아서 자격 증명이 만료된 경우.
  • isEnabled
    • 계정이 활성화 여부. (true : 활성화, false : 비활성화.)
    • Ex) User모델에 마지막 로그인 시점 필드를 추가하고, 해당 메서드에서 user.getLoginDate()로 값을 추출해서
      현재 시간 - 마지막 로그인 시간의 값이 특정 값을 넘었다면 return false. (즉 비활성화)
  • 이제 Authentication 객체를 만들어야됨.

2-4. PrincipalDetailsService

@Service
public class PrincipalDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return null;
    }
}
  • 시큐리티 설정(SecurityConfig)에서 loginProcessingUrl("/login")으로 해놨기 때문에 /login 요청이 오면 자동으로 UserDetailsService타입으로 IoC되어 있는 loadUserByUsername 메서드가 실행됨.
    • 즉, 인증 과정에서 사용자 정보를 가져오기 위해 UserDetailsService 타입의 을 찾아 loadUserByUsername 메서드를 호출.
      • 이 메서드는 사용자 이름을 기반으로 사용자 정보를 조회하고, 해당 정보를 UserDetails 객체로 반환함.
  • loadUserByUsername 메서드파라미터에 있는 usernameloginForm.html에 있는 name="username"과 매칭됨.
    • <input type="text" name="username" placeholder="Username"/>
  • 만약 loginForm.html에서 `name="username2"로 해버리면 매칭이 안됨.
    • 이럴 경우 SecurityConfig(시큐리티 설정)에서 .usernameParameter("username2")로 지정해줘야됨.
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
      @Override
      protected void configure(HttpSecurity httpSecurity) throws Exception {
          httpSecurity.csrf().disable();
          httpSecurity.authorizeRequests()
                  
    					....
                  
                  .loginProcessingUrl("/login")                                      // '/login' 주소가 호출되면 시큐리티가 대신 로그인을 처리함. -> 그래서 컨트롤러에 login 메서드가 필요없음.
                  .usernameParameter("username2")
                  .defaultSuccessUrl("/");                                            // 로그인 성공 시 메인 페이지로 이동.
      }
    

2-5. 현재까지의 로직.

  • 사용자가 로그인 버튼을 누름.

    POST방식으로 /login이 호출됨.

    스프링 IoC컨테이너에서 UserDetailsService 타입으로 등록된 걸 찾음.
    즉, PrincipalDetailsService

    그리고 loadUserByUsername 메서드를 호출.

    메서드 파라미터로 해당 유저의 username을 받음.

2-6. PrincipalDetailsService, UserRepository 수정.

PrincipalDetailsService

@Service    // 빈등록.
public class PrincipalDetailsService implements UserDetailsService {
    @Autowired
    private UserRepository repository;

	   @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User userEntity = userRepository.findByUsername(username);
        return null;
    }
}
  • @Autowired를 통해 DI 의존성 주입.
  • UserRepository를 통해 findByUsername 메서드를 호출해서 해당 유저의 정보를 조회.
    • 리파지토리에는 기본적인 CRUD메서드만 있으므로 findByUsername를 만들어줘야함.

UserRepository

public interface UserRepository extends JpaRepository<User, Integer> {

    User findByUsername(String username);
}
  • JPA Query Method
    • findBy까지는 규칙, 그 뒤에 있는 Username은 문법.
    • 즉, SELECT * FROM user WHERE username = ? 가 호출이 됨.
      (?에는 파라미터에 있는 username이 삽입됨.)

PrincipalDetailsService

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User userEntity = userRepository.findByUsername(username);
        if (userEntity != null) {
            return new PrincipalDetails(userEntity);
        }
        return null;
    }
  • SecurityContext Authentication UserDetails
    • 인증이 된 후 Authentication이 저장되는 곳은 SecurityContext.
    • 그래서 SecurityContextHolderSecurityContext를 감싸고 있는 클래스임.
      (저장한다는 개념 X)
    • HttpSession은 인증 후 결과 정보가 있는 SecurityContext를 담아놓고 사용자가 계속 인증을 유지하기 위한 목적으로 사용되고 있지만 SecurityContextHolderHttpSession에서 SecurityContext를 꺼내어 다시 ThreadLocal에 저장하고 있음.
      • SecurityContext가 최종 저장되는 곳은 ThreadLocal.
      • HttpSession이 인증에 성공할 경우에 SecurityContext를 저장.
      • 결국은 SecurityContextHolder를 사용해서 HttpSession에서 SecurityContext를 꺼내어 ThreadLocal에 다시 저장.
    • 따라서 어디에서나 Authentication을 참조할 수 있도록 SecurityContextHolder.getContext().getAuthentication()와 같은 구문을 사용할 수 있게 됨.
  • 다만 스프링 시큐리티에서는 해당 사용자인증이 되었는지 아닌지판별할 때 세션을 직접 참조하는 것이 아닌 ThreadLocal에 저장된 SecurityContext 객체를 꺼내어 이 속에 Authentication 객체존재하는지 여부를 보기 때문에 세션과는 직접적인 상관이 없음.
    • 또한 스프링 시큐리티에서는 세션을 아예 사용하지 않고도 인증을 구현하는 일종의 토큰 방식을 사용할 수도 있기 때문에 세션에 의존적이지 않음.

  • localhost:8080/logout으로 로그아웃을 한 뒤에 로그인.

  • 인덱스 페이지로 가짐.

    • 왜냐하면 SecurityConfigloginPage("/loginForm")로 설정하고 SuccessUrl/로 설정했으므로.
    .and().formLogin().loginPage("/loginForm")
                  .loginProcessingUrl("/login")  
                  .defaultSuccessUrl("/");
    
  • 다시 로그아웃 한 후, /user로 접속하면.


  • /loginForm으로 오게됨.
  • 여기서 다시 로그인을 해 보면 자동으로 /user로 이동됨.
    즉, 내가 원래 가려던 페이지로 가게됨.

  • 이제 로그인이 정상적으로 되고 콘솔에도 값이 제대로 찍혀있음을 확인.

3. UserDetails

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

4. GrantedAuthority

public interface GrantedAuthority extends Serializable {
    String getAuthority();
}

5. User

  • Spring Security에 정의 되어 있는 User클래스.
public class User implements UserDetails, CredentialsContainer {
    private static final long serialVersionUID = 550L;
    private static final Log logger = LogFactory.getLog(User.class);
    private String password;
    private final String username;
    private final Set<GrantedAuthority> authorities;
    private final boolean accountNonExpired;
    private final boolean accountNonLocked;
    private final boolean credentialsNonExpired;
    private final boolean enabled;

    public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        this(username, password, true, true, true, true, authorities);
    }

    public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
        Assert.isTrue(username != null && !"".equals(username) && password != null, "Cannot pass null or empty values to constructor");
        this.username = username;
        this.password = password;
        this.enabled = enabled;
        this.accountNonExpired = accountNonExpired;
        this.credentialsNonExpired = credentialsNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
    }

    public Collection<GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    public String getPassword() {
        return this.password;
    }

    public String getUsername() {
        return this.username;
    }

    public boolean isEnabled() {
        return this.enabled;
    }

    public boolean isAccountNonExpired() {
        return this.accountNonExpired;
    }

    public boolean isAccountNonLocked() {
        return this.accountNonLocked;
    }

    public boolean isCredentialsNonExpired() {
        return this.credentialsNonExpired;
    }

    public void eraseCredentials() {
        this.password = null;
    }

    private static SortedSet<GrantedAuthority> sortAuthorities(Collection<? extends GrantedAuthority> authorities) {
        Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
        SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet(new AuthorityComparator());
        Iterator var2 = authorities.iterator();

        while(var2.hasNext()) {
            GrantedAuthority grantedAuthority = (GrantedAuthority)var2.next();
            Assert.notNull(grantedAuthority, "GrantedAuthority list cannot contain any null elements");
            sortedAuthorities.add(grantedAuthority);
        }

        return sortedAuthorities;
    }

    public boolean equals(Object obj) {
        return obj instanceof User ? this.username.equals(((User)obj).username) : false;
    }

    public int hashCode() {
        return this.username.hashCode();
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(this.getClass().getName()).append(" [");
        sb.append("Username=").append(this.username).append(", ");
        sb.append("Password=[PROTECTED], ");
        sb.append("Enabled=").append(this.enabled).append(", ");
        sb.append("AccountNonExpired=").append(this.accountNonExpired).append(", ");
        sb.append("credentialsNonExpired=").append(this.credentialsNonExpired).append(", ");
        sb.append("AccountNonLocked=").append(this.accountNonLocked).append(", ");
        sb.append("Granted Authorities=").append(this.authorities).append("]");
        return sb.toString();
    }

    public static UserBuilder withUsername(String username) {
        return builder().username(username);
    }

    public static UserBuilder builder() {
        return new UserBuilder();
    }

    /** @deprecated */
    @Deprecated
    public static UserBuilder withDefaultPasswordEncoder() {
        logger.warn("User.withDefaultPasswordEncoder() is considered unsafe for production and is only intended for sample applications.");
        PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
        UserBuilder var10000 = builder();
        Objects.requireNonNull(encoder);
        return var10000.passwordEncoder(encoder::encode);
    }

    public static UserBuilder withUserDetails(UserDetails userDetails) {
        return withUsername(userDetails.getUsername()).password(userDetails.getPassword()).accountExpired(!userDetails.isAccountNonExpired()).accountLocked(!userDetails.isAccountNonLocked()).authorities(userDetails.getAuthorities()).credentialsExpired(!userDetails.isCredentialsNonExpired()).disabled(!userDetails.isEnabled());
    }

    public static final class UserBuilder {
        private String username;
        private String password;
        private List<GrantedAuthority> authorities;
        private boolean accountExpired;
        private boolean accountLocked;
        private boolean credentialsExpired;
        private boolean disabled;
        private Function<String, String> passwordEncoder;

        private UserBuilder() {
            this.passwordEncoder = (password) -> {
                return password;
            };
        }

        public UserBuilder username(String username) {
            Assert.notNull(username, "username cannot be null");
            this.username = username;
            return this;
        }

        public UserBuilder password(String password) {
            Assert.notNull(password, "password cannot be null");
            this.password = password;
            return this;
        }

        public UserBuilder passwordEncoder(Function<String, String> encoder) {
            Assert.notNull(encoder, "encoder cannot be null");
            this.passwordEncoder = encoder;
            return this;
        }

        public UserBuilder roles(String... roles) {
            List<GrantedAuthority> authorities = new ArrayList(roles.length);
            String[] var3 = roles;
            int var4 = roles.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                String role = var3[var5];
                Assert.isTrue(!role.startsWith("ROLE_"), () -> {
                    return role + " cannot start with ROLE_ (it is automatically added)";
                });
                authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
            }

            return this.authorities((Collection)authorities);
        }

        public UserBuilder authorities(GrantedAuthority... authorities) {
            return this.authorities((Collection)Arrays.asList(authorities));
        }

        public UserBuilder authorities(Collection<? extends GrantedAuthority> authorities) {
            this.authorities = new ArrayList(authorities);
            return this;
        }

        public UserBuilder authorities(String... authorities) {
            return this.authorities((Collection)AuthorityUtils.createAuthorityList(authorities));
        }

        public UserBuilder accountExpired(boolean accountExpired) {
            this.accountExpired = accountExpired;
            return this;
        }

        public UserBuilder accountLocked(boolean accountLocked) {
            this.accountLocked = accountLocked;
            return this;
        }

        public UserBuilder credentialsExpired(boolean credentialsExpired) {
            this.credentialsExpired = credentialsExpired;
            return this;
        }

        public UserBuilder disabled(boolean disabled) {
            this.disabled = disabled;
            return this;
        }

        public UserDetails build() {
            String encodedPassword = (String)this.passwordEncoder.apply(this.password);
            return new User(this.username, encodedPassword, !this.disabled, !this.accountExpired, !this.credentialsExpired, !this.accountLocked, this.authorities);
        }
    }

    private static class AuthorityComparator implements Comparator<GrantedAuthority>, Serializable {
        private static final long serialVersionUID = 550L;

        private AuthorityComparator() {
        }

        public int compare(GrantedAuthority g1, GrantedAuthority g2) {
            if (g2.getAuthority() == null) {
                return -1;
            } else {
                return g1.getAuthority() == null ? 1 : g1.getAuthority().compareTo(g2.getAuthority());
            }
        }
    }
}

6. UserDetailsService

public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
profile
Every cloud has a silver lining.

0개의 댓글