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> | </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 관련 ❗