Springboot에 Spring security를 이용하여 인증처리를 해보도록 하겠습니다.
사용된 기술은 아래와 같습니다.
1. Springboot 1.5.3
2. Spring 4.3.9
3. Spring Security 4.2.3
4. Thymeleaf 2.1.5
5. Tyhmeleaf Extras Spring Security4
6. Embed tomcat
7. Maven
8. Java 8
1. Project Directroy
2. Project Dependencies
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>devan</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>springboot-jpa-security-login</name> <description>devan</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.4.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity4</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> | cs |
3. Spring Security
WebSecurityConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | package com.example.auth.config; 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.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception{ // h2 console 사용을 위한 설정 http.csrf().ignoringAntMatchers("/h2console/**"); http.headers().frameOptions().sameOrigin(); http .authorizeRequests() // 해당 url을 허용한다. .antMatchers("/resources/**","/loginError","/registration","/h2console/**").permitAll() // admin 폴더에 경우 admin 권한이 있는 사용자에게만 허용 .antMatchers("/admin/**").hasAuthority("ADMIN") // user 폴더에 경우 user 권한이 있는 사용자에게만 허용 .antMatchers("/user/**").hasAuthority("USER") .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .successHandler(new CustomAuthenticationSuccess()) // 로그인 성공 핸들러 .failureHandler(new CustomAuthenticationFailure()) // 로그인 실패 핸들러 .permitAll() .and() .logout() .permitAll() .and() .exceptionHandling().accessDeniedPage("/403"); // 권한이 없을경우 해당 url로 이동 } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); } } | cs |
CustomAuthenticationSuccess.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package com.example.auth.config; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.security.web.DefaultRedirectStrategy; import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; public class CustomAuthenticationSuccess implements AuthenticationSuccessHandler{ private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { redirectStrategy.sendRedirect(request, response, "/main"); } } | cs |
CustomAuthenticationFailure.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package com.example.auth.config; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; public class CustomAuthenticationFailure implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { // 로그인 실패시 username을 화면에 그대로 출력하도록 하기 위해 아래와 같은 // 핸들러를 작성하였는데 더 쉬운 방법 아시는분?? request.setAttribute("username", request.getParameter("username")); request.getRequestDispatcher("/loginError").forward(request, response); } } | cs |
4. Controller 생성
WebController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | package com.example.auth.web; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.example.auth.web.model.User; import com.example.auth.web.service.SecurityService; import com.example.auth.web.service.UserService; @Controller public class WebController { @Autowired UserService userService; @Autowired private SecurityService securityService; @RequestMapping(value="/main") public String main(){ return "main"; } // 로그인 @RequestMapping("/login") public String login(Model model, String error, String logout, HttpServletRequest request ){ if (logout != null){ model.addAttribute("logout", "You have been logged out successfully."); } return "login"; } // 로그인 실패시 @RequestMapping(value="/loginError") public String loginError(Model model, String username ){ model.addAttribute("error", "Your username and password is invalid."); model.addAttribute("username",username); return "login"; } // 회원가입폼 @RequestMapping(value="/registration",method=RequestMethod.GET) public String registration(Model model){ model.addAttribute("userForm", new User()); return "registration"; } // 회원가입 처리 후 로그인 @RequestMapping(value="/registration",method=RequestMethod.POST) public String registration(@ModelAttribute("userForm") User userForm, BindingResult bindingResult, Model model ,String[] roles ){ String password = userForm.getPassword(); userService.saveUser(userForm,roles); securityService.autologin(userForm.getUsername(),password); return "redirect:/main"; } // admin 사용자 테스트 @RequestMapping("/admin") public String admin(){ return "/admin/admin"; } // user 사용자 테스트 @RequestMapping("/user") public String user(){ return "/user/user"; } // 권한없는 페이지를 들어갔을때 @RequestMapping("/403") public String access(){ return "/access"; } } | cs |
5. Entity Model
User.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package com.example.auth.web.model; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.Table; import lombok.Data; @Entity @Table(name = "user") @Data public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String username; private String password; @ManyToMany(cascade=CascadeType.ALL) @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) private Set<Role> roles; } | cs |
Role.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package com.example.auth.web.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import lombok.Data; @Entity @Table(name="role") @Data public class Role { @Id @GeneratedValue private Long id; private String name; public Role(){ } public Role(String name){ this.name = name; } } | cs |
6. Repository
UserRepository.java
1 2 3 4 5 6 7 8 9 | package com.example.auth.web.repository; import org.springframework.data.jpa.repository.JpaRepository; import com.example.auth.web.model.User; public interface UserRepository extends JpaRepository<User, Long>{ public User findByUsername(String username); } | cs |
7. Service
SecurityService.java (interface)
1 2 3 4 5 6 | package com.example.auth.web.service; public interface SecurityService { String findLoggedInUsername(); void autologin(String username, String password); } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | package com.example.auth.web.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Service; import lombok.extern.slf4j.Slf4j; @Service @Slf4j public class SecurityServiceImpl implements SecurityService { @Autowired private UserDetailsService userDetailsService; @Autowired private AuthenticationManager authenticationManager; @Override public String findLoggedInUsername() { Object userDetails = SecurityContextHolder.getContext().getAuthentication().getDetails(); if (userDetails instanceof UserDetails) { return ((UserDetails) userDetails).getUsername(); } return null; } @Override public void autologin(String username, String password) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities()); authenticationManager.authenticate(usernamePasswordAuthenticationToken); if (usernamePasswordAuthenticationToken.isAuthenticated()) { SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); log.debug(String.format("Auto login %s successfully!", password)); } } } | cs |
1 2 3 4 5 6 7 8 | package com.example.auth.web.service; import com.example.auth.web.model.User; public interface UserService { void saveUser(User user,String[] roles); User findByUsername(String username); } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | package com.example.auth.web.service; import java.util.HashSet; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import com.example.auth.web.model.Role; import com.example.auth.web.model.User; import com.example.auth.web.repository.UserRepository; import lombok.extern.slf4j.Slf4j; @Service @Slf4j public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; @Override public void saveUser(User user,String[] roles) { user.setPassword(bCryptPasswordEncoder.encode(user.getPassword())); Set<Role> rolesSet = new HashSet<Role>(); for(String role:roles){ rolesSet.add(new Role(role)); } user.setRoles(rolesSet); userRepository.save(user); } @Override public User findByUsername(String username) { return userRepository.findByUsername(username); } } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | package com.example.auth.web.service; import java.util.HashSet; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; 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.Component; import com.example.auth.web.model.Role; import com.example.auth.web.model.User; import com.example.auth.web.repository.UserRepository; @Component public class UserDetailsServiceImpl implements UserDetailsService { @Autowired UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); Set<GrantedAuthority> grantedAuthorities = new HashSet<>(); for (Role role : user.getRoles()) { grantedAuthorities.add(new SimpleGrantedAuthority(role.getName())); } return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities); } } | cs |
8. thymeleaf
login.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8" /> <title>Login</title> </head> <body> login <form th:action="@{/login}" method="post"> name: <input type="text" name="username" id="username" th:value="${username}" /><br /> p w: <input type="password" name="password" id="password" /><br /> <div th:if="${logout}">You have been logged out successfully</div> <input type="submit" value="sign in" /> </form> <a href="/registration">registration</a> </body> </html> |
registration.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8" /> <title>Registration</title> </head> <body> Registration <form name="userForm" th:action="@{/registration}" method="post"> name : <input type="text" name="username" id="username"/> password : <input type="text" name="password" id="password"/> <br/> ADMIN <input type="checkbox" name="roles" value="ADMIN"/> | USER <input type="checkbox" name="roles" value="USER" /> <hr/> <input type="submit" value="join" /> </form> </body> </html> | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> <head> <meta charset="UTF-8" /> <title>Main</title> </head> <body> <span sec:authentication="name"></span> <span sec:authentication="principal.authorities"></span> <div sec:authorize="hasAuthority('ADMIN')">- only admin!!!!</div> <div sec:authorize="hasAuthority('USER')">- only user</div> <form th:action="@{/logout}" method="post"> <input type="submit" value="Sign Out" /> </form> <a th:attr="href=@{/admin}">admin</a> <a th:attr="href=@{/user}">user</a> </body> </html> | cs |
access.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"/> <title></title> </head> <body> <script type="text/javascript"> <!-- // 403에러메세지가 발생했을 경우 이전페이지로이동한다. history.back(-1); //--> </script> </body> </html> | cs |
9.Application.properties
1 2 3 4 5 6 7 8 9 | server.port=1322 hibernate.hbm2ddl.auto=update spring.datasource.driverClassName=org.h2.Driver spring.datasource.url=jdbc:h2:~/db1;DB_CLOSE_ON_EXIT=FALSE spring.h2.console.enabled=true spring.h2.console.path=/h2console | cs |
10.demo
로그인 화면
로그인에 실패 할 경우 아래와 같이 표시
ADMIN권한으로 회원가입
회원가입후 name과 권한 표시
admin 링크를 눌러 admin 페이지로 들어가는지 확인(user 링크를 누를 경우 다시 main으로 돌아온다)
USER권한으로 회원가입
이름과 USER 권한을 확인할 수 있다.
마찬가지로 user 페이지로 이동하여 정상 출력되는지 확인 (admin에 경우 권한이 없으므로 main으로 이동)
마지막으로 모든권한을 가진 USER를 생성해본다
ADMIN,USER 권한 모두 가진걸 볼수 있다. admin,user 페이지 모두 들어가지는 걸 확인할 수 있다.
h2console로 들어가 DB에 입력된 내용을 확인해 보자
비밀번호가 암호화 되어 들어가 있는것을 확인 할 수 있다.
11. Conculution
SpringSecurity와 JPA를 이용하여 간단한 로그인 인증과 권한 설정으로 로그인 처리와 특정페이지만 접속이 가능하도록 설정해 보았다. 본문에 설명은 하지 않았지만 html form 마다 _csrf 값이 자동으로 주입되며 서버에 요청이 갈때 해당값을 넘기도록 하여 사이트 간 요청 위조을 막아준다. 기존에 개발자가 직접해야 할 일들을 security에서 많은 처리를 해준다는 사실을 알수 있다. 소스는 아래 github에서 확인 할 수 있다.
https://github.com/beans9/springboot-security-jpa-thymeleaf
12. Reference
http://www.mkyong.com/spring-boot/spring-boot-spring-security-thymeleaf-example/
'Programming > Springboot' 카테고리의 다른 글
[springboot] @autowired 안에서 @value로 호출한 값 못 읽을때 (0) | 2019.03.13 |
---|---|
[springboot] jar 실행시 profile 선택 (0) | 2019.01.09 |
[springboot] mybatis-multiple-datasource / db 여러개 사용 (0) | 2017.03.29 |
[springboot] springboot starter maven dependency (0) | 2017.03.27 |
[springboot] jpa multiple datasource (0) | 2017.03.24 |