├── .gitignore ├── .travis.yml ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── okihouse │ │ ├── SpringSecurityApplication.java │ │ ├── controller │ │ ├── UserApiController.java │ │ └── UserController.java │ │ ├── entity │ │ ├── Role.java │ │ ├── RolePermission.java │ │ └── User.java │ │ ├── repository │ │ ├── RolePermissionRepository.java │ │ └── UserRepository.java │ │ ├── security │ │ ├── Security.java │ │ ├── api │ │ │ ├── filter │ │ │ │ └── ApiTokenAuthenticationProcessingFilter.java │ │ │ └── token │ │ │ │ ├── ApiRefreshTokenController.java │ │ │ │ ├── ApiTokenFactory.java │ │ │ │ └── data │ │ │ │ └── ApiTokenData.java │ │ ├── common │ │ │ ├── data │ │ │ │ └── SecurityUrlData.java │ │ │ ├── details │ │ │ │ └── UserDetailsServiceImpl.java │ │ │ ├── entrypoint │ │ │ │ └── SecurityUserAccessEntryPoint.java │ │ │ ├── filter │ │ │ │ └── SecurityUserLoginProcessingFilter.java │ │ │ ├── handler │ │ │ │ ├── SecurityUserAcessDeniedHandler.java │ │ │ │ └── SecurityUserLoginHandler.java │ │ │ ├── provider │ │ │ │ └── SecurityAuthenticationProvider.java │ │ │ └── repository │ │ │ │ ├── PersistTokenRepository.java │ │ │ │ └── vo │ │ │ │ └── RememberToken.java │ │ └── web │ │ │ └── handler │ │ │ └── WebSecurityUserLogoutHandler.java │ │ ├── util │ │ ├── AjaxUtils.java │ │ └── JsonUtils.java │ │ └── vo │ │ └── SuccessVO.java ├── resources │ ├── META-INF │ │ └── additional-spring-configuration-metadata.json │ ├── application.yml │ ├── logback.xml │ └── user.sql └── webapp │ └── WEB-INF │ ├── error │ └── accessdenied.jsp │ ├── login.jsp │ └── user │ ├── admin.jsp │ └── mypage.jsp └── test └── java └── com └── okihouse ├── AllTests.java ├── api └── ApiWebAppSecurityTests.java └── web └── WebFormSecurityTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | services: 7 | - redis-server 8 | 9 | before_script: sudo redis-server /etc/redis/redis.conf --port 6379 10 | 11 | script: mvn clean -Dmaven.test.skip=true 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/okihouse/spring-boot-security-with-redis-jwt-for-restapi-webapp.svg?branch=master)](https://travis-ci.org/okihouse/spring-boot-security-with-redis) 2 | 3 | Demo 4 | ========= 5 | 6 | https://youtu.be/YwP1v1VYZes 7 | 8 | [![Demo](http://i3.ytimg.com/vi/YwP1v1VYZes/maxresdefault.jpg)](https://youtu.be/YwP1v1VYZes "Demo") 9 | 10 | 11 | Description 12 | ========= 13 | 14 | Web Application Authentication 15 | 1. Form Login or Ajax Login 16 | 2. Remember-Me 17 | 3. Session Management 18 | 4. Authentication with role(authority) 19 | 5. CSRF Protection 20 | 21 | Restful API Authentication 22 | 1. Token Authentication 23 | 2. CORS 24 | 3. Authentication with role(authority) 25 | 26 | Requirements 27 | ===== 28 | 29 | * Java 1.8 30 | * [Spring boot](http://projects.spring.io/spring-boot/) 1.2.8+ (spring-boot-starter-redis) 31 | * [Redis](http://redis.io/) 2.4+ 32 | * lombok 33 | 34 | Watch Out! 35 | ===== 36 | 37 | * Update host and port in `application.yml` for `Redis`. 38 | 39 | Run 40 | === 41 | 42 | ```bash 43 | mvn spring-boot:run 44 | ``` 45 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.okihouse 7 | security 8 | 0.0.1-SNAPSHOT 9 | war 10 | 11 | spring_security_redis 12 | Spring Security with remember me on redis 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.5.7.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-security 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-web 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-data-jpa 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-data-redis 46 | 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-configuration-processor 51 | true 52 | 53 | 54 | 55 | org.springframework.session 56 | spring-session 57 | 58 | 59 | 60 | org.springframework.security 61 | spring-security-taglibs 62 | 63 | 64 | 65 | org.springframework.security 66 | spring-security-web 67 | 68 | 69 | 70 | io.jsonwebtoken 71 | jjwt 72 | 0.7.0 73 | 74 | 75 | 76 | org.apache.tomcat.embed 77 | tomcat-embed-jasper 78 | 79 | 80 | 81 | javax.servlet 82 | jstl 83 | 84 | 85 | 86 | javax.servlet 87 | javax.servlet-api 88 | 89 | 90 | 91 | javax.servlet.jsp 92 | jsp-api 93 | 2.1 94 | 95 | 96 | 97 | org.projectlombok 98 | lombok 99 | true 100 | 101 | 102 | 103 | org.apache.commons 104 | commons-lang3 105 | 3.4 106 | 107 | 108 | 109 | com.h2database 110 | h2 111 | runtime 112 | 113 | 114 | 115 | org.springframework.boot 116 | spring-boot-starter-test 117 | test 118 | 119 | 120 | 121 | org.springframework.security 122 | spring-security-test 123 | test 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | org.springframework.boot 132 | spring-boot-maven-plugin 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/SpringSecurityApplication.java: -------------------------------------------------------------------------------- 1 | package com.okihouse; 2 | 3 | import javax.annotation.PostConstruct; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.boot.builder.SpringApplicationBuilder; 9 | import org.springframework.boot.web.support.SpringBootServletInitializer; 10 | import org.springframework.data.redis.core.StringRedisTemplate; 11 | 12 | @SpringBootApplication 13 | public class SpringSecurityApplication extends SpringBootServletInitializer { 14 | 15 | @Autowired 16 | private StringRedisTemplate stringRedisTemplate; 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(SpringSecurityApplication.class, args); 20 | } 21 | 22 | @PostConstruct 23 | public void connection() { 24 | try { 25 | stringRedisTemplate.getConnectionFactory().getConnection(); 26 | } catch (Exception e) { 27 | System.out.println("-------------------------------------------------------------------------------------------"); 28 | System.out.println("- Redis host and port is not availables. please check application configuration file. -"); 29 | System.out.println("-------------------------------------------------------------------------------------------"); 30 | System.exit(-1); 31 | } 32 | } 33 | 34 | @Override 35 | protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { 36 | return builder.sources(SpringSecurityApplication.class); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/controller/UserApiController.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.controller; 2 | 3 | import org.springframework.security.access.prepost.PreAuthorize; 4 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestMethod; 7 | import org.springframework.web.bind.annotation.ResponseBody; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | /** 11 | * Created by okihouse16 on 2017. 12. 22.. 12 | */ 13 | @RestController 14 | public class UserApiController { 15 | 16 | @PreAuthorize("hasAnyAuthority('READ_MYPAGE')") 17 | @RequestMapping(value = "/api/mypage", method = RequestMethod.GET) 18 | @ResponseBody 19 | public UsernamePasswordAuthenticationToken mypage(UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) { 20 | return usernamePasswordAuthenticationToken; 21 | } 22 | 23 | @PreAuthorize("hasAuthority('READ_ADMIN')") 24 | @RequestMapping(value = "/api/admin", method = RequestMethod.GET) 25 | @ResponseBody 26 | public UsernamePasswordAuthenticationToken admin(UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) { 27 | return usernamePasswordAuthenticationToken; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.controller; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import javax.servlet.http.Cookie; 7 | 8 | import org.apache.catalina.servlet4preview.http.HttpServletRequest; 9 | import org.springframework.security.access.prepost.PreAuthorize; 10 | import org.springframework.security.core.Authentication; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.ui.Model; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestMethod; 15 | 16 | @Controller 17 | public class UserController { 18 | 19 | @RequestMapping(value = {"/", "/login"}, method = RequestMethod.GET) 20 | public String login() { 21 | return "login"; 22 | } 23 | 24 | @RequestMapping(value = {"/error/accessdenied"}, method = RequestMethod.GET) 25 | public String accessDenied() { 26 | return "error/accessdenied"; 27 | } 28 | 29 | @PreAuthorize("hasAuthority('READ_MYPAGE')") 30 | @RequestMapping(value = "/mypage", method = RequestMethod.GET) 31 | public String mypage(Model model, HttpServletRequest request, Authentication authentication) { 32 | List cookies = new ArrayList<>(); 33 | Cookie[] originCookies = request.getCookies(); 34 | if (cookies != null) { 35 | for (Cookie cookie : originCookies) { 36 | cookies.add(String.format("%s=%s", cookie.getName(), cookie.getValue())); 37 | } 38 | } 39 | 40 | model.addAttribute("authentication", authentication); 41 | model.addAttribute("sessionId", request.getSession().getId()); 42 | model.addAttribute("cookie", cookies); 43 | return "user/mypage"; 44 | } 45 | 46 | @PreAuthorize("hasAuthority('READ_ADMIN')") 47 | @RequestMapping(value = "/admin", method = RequestMethod.GET) 48 | public String admin(Model model, Authentication authentication) { 49 | model.addAttribute("authentication", authentication); 50 | return "user/admin"; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/entity/Role.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.entity; 2 | 3 | import java.util.List; 4 | 5 | import javax.persistence.CascadeType; 6 | import javax.persistence.Column; 7 | import javax.persistence.Entity; 8 | import javax.persistence.FetchType; 9 | import javax.persistence.Id; 10 | import javax.persistence.JoinColumn; 11 | import javax.persistence.ManyToOne; 12 | import javax.persistence.OneToMany; 13 | import javax.persistence.Table; 14 | 15 | import lombok.Data; 16 | 17 | /** 18 | * Created by okihouse16 on 2017. 12. 21.. 19 | */ 20 | @Data 21 | @Entity 22 | @Table(name = "ROLE") 23 | public class Role { 24 | 25 | @Id 26 | @Column(name = "roleId") 27 | private Long id; 28 | 29 | @Column(name = "role") 30 | private String role; 31 | 32 | @ManyToOne 33 | @JoinColumn(name = "id") 34 | private User user; 35 | 36 | @OneToMany(mappedBy = "role", fetch = FetchType.EAGER ,cascade = CascadeType.PERSIST) 37 | private List permissions; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/entity/RolePermission.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.entity; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.*; 6 | 7 | /** 8 | * Created by okihouse16 on 2017. 12. 21.. 9 | */ 10 | @Data 11 | @Entity 12 | @Table(name = "ROLE_PERMISSION") 13 | public class RolePermission { 14 | 15 | @Id 16 | @Column(name = "permissionId") 17 | private Long permissionId; 18 | 19 | @Column(name = "permission") 20 | private String permission; 21 | 22 | @ManyToOne 23 | @JoinColumn(name = "roleId") 24 | private Role role; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.entity; 2 | 3 | import java.util.List; 4 | 5 | import javax.persistence.*; 6 | 7 | import lombok.Data; 8 | 9 | /** 10 | * Created by okihouse16 on 2017. 12. 21.. 11 | */ 12 | @Data 13 | @Entity 14 | @Table(name="USER") 15 | public class User { 16 | 17 | @Id 18 | @Column(name = "id") 19 | @GeneratedValue(strategy = GenerationType.AUTO) 20 | private Long id; 21 | 22 | @Column(name = "username") 23 | private String username; 24 | 25 | @Column(name = "password") 26 | private String password; 27 | 28 | @OneToMany(mappedBy = "user", fetch = FetchType.EAGER ,cascade = CascadeType.PERSIST) 29 | private List roles; 30 | 31 | 32 | @Override 33 | public String toString() { 34 | return "User{" + 35 | "id=" + id + 36 | ", username='" + username + '\'' + 37 | ", password='" + password + '}'; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/repository/RolePermissionRepository.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.repository; 2 | 3 | import com.okihouse.entity.RolePermission; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | import org.springframework.data.repository.query.Param; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Created by okihouse16 on 2018. 1. 29.. 12 | */ 13 | public interface RolePermissionRepository extends JpaRepository { 14 | 15 | @Query(value = "SELECT permission FROM ROLE_PERMISSION WHERE role_id IN (:roleIds)", nativeQuery = true) 16 | List permissions(@Param("roleIds") List roleIds); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.repository; 2 | 3 | import com.okihouse.entity.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | /** 7 | * Created by okihouse16 on 2017. 12. 22.. 8 | */ 9 | public interface UserRepository extends JpaRepository { 10 | 11 | User findFirstByUsername(String username); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/security/Security.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.security; 2 | 3 | import com.okihouse.repository.RolePermissionRepository; 4 | import com.okihouse.repository.UserRepository; 5 | import com.okihouse.security.api.filter.ApiTokenAuthenticationProcessingFilter; 6 | import com.okihouse.security.api.token.ApiTokenFactory; 7 | import com.okihouse.security.common.data.SecurityUrlData; 8 | import com.okihouse.security.common.entrypoint.SecurityUserAccessEntryPoint; 9 | import com.okihouse.security.common.filter.SecurityUserLoginProcessingFilter; 10 | import com.okihouse.security.common.handler.SecurityUserAcessDeniedHandler; 11 | import com.okihouse.security.common.handler.SecurityUserLoginHandler; 12 | import com.okihouse.security.common.provider.SecurityAuthenticationProvider; 13 | import com.okihouse.security.common.repository.PersistTokenRepository; 14 | import com.okihouse.security.web.handler.WebSecurityUserLogoutHandler; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 17 | import org.springframework.context.annotation.Bean; 18 | import org.springframework.context.annotation.Configuration; 19 | import org.springframework.security.authentication.AuthenticationManager; 20 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 21 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 22 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 23 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 24 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 25 | import org.springframework.security.core.userdetails.UserDetailsService; 26 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 27 | import org.springframework.security.crypto.password.PasswordEncoder; 28 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 29 | import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices; 30 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 31 | import org.springframework.security.web.util.matcher.OrRequestMatcher; 32 | import org.springframework.security.web.util.matcher.RequestMatcher; 33 | import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; 34 | 35 | import javax.servlet.http.HttpServletRequest; 36 | import java.util.Arrays; 37 | 38 | @Configuration 39 | @EnableWebSecurity 40 | @EnableRedisHttpSession 41 | @EnableAutoConfiguration 42 | @EnableGlobalMethodSecurity(prePostEnabled = true) 43 | public class Security extends WebSecurityConfigurerAdapter { 44 | 45 | @Autowired private AuthenticationManager authenticationManager; 46 | 47 | @Autowired private SecurityUserLoginHandler securityUserLoginHandler; 48 | 49 | @Autowired private SecurityUserAcessDeniedHandler securityUserAcessDeniedHandler; 50 | 51 | @Autowired private SecurityUserAccessEntryPoint securityUserAccessEntryPoint; 52 | 53 | @Autowired private WebSecurityUserLogoutHandler webSecurityUserLogoutHandler; 54 | 55 | @Autowired private PersistTokenRepository persistenceTokenRepository; 56 | 57 | @Autowired private UserDetailsService userDetailsService; 58 | 59 | @Autowired private SecurityAuthenticationProvider securityAuthenticationProvider; 60 | 61 | @Autowired private ApiTokenFactory apiTokenFactory; 62 | 63 | @Autowired private UserRepository userRepository; 64 | 65 | @Autowired private RolePermissionRepository rolePermissionRepository; 66 | 67 | @Autowired private SecurityUrlData securityUrlData; 68 | 69 | private final String rememberKey = "remember-me"; 70 | 71 | @Override 72 | protected void configure(HttpSecurity httpSecurity) throws Exception { 73 | httpSecurity.csrf().ignoringAntMatchers("/api/*"); // Api use token authentication. 74 | 75 | httpSecurity.sessionManagement(); 76 | httpSecurity.authorizeRequests() 77 | .antMatchers( 78 | securityUrlData.getLogin() 79 | , securityUrlData.getLogout() 80 | , securityUrlData.getRefreshToken() 81 | ).permitAll() 82 | .anyRequest().authenticated() 83 | .and() 84 | .logout() 85 | .logoutRequestMatcher(new AntPathRequestMatcher(securityUrlData.getLogout())) 86 | .deleteCookies("JSESSIONID", rememberKey) 87 | .logoutSuccessHandler(webSecurityUserLogoutHandler) 88 | .and() 89 | .rememberMe() 90 | .key(rememberKey) 91 | .rememberMeParameter(rememberKey) 92 | .rememberMeServices(persistentTokenBasedRememberMeServices()) 93 | .tokenValiditySeconds(3600) 94 | .and() 95 | .exceptionHandling() 96 | .authenticationEntryPoint(securityUserAccessEntryPoint) 97 | .accessDeniedHandler(securityUserAcessDeniedHandler) 98 | .and() 99 | .addFilterBefore(buildWebSecurityUserLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) 100 | .addFilterBefore(buildApiSecurityUserLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) 101 | .addFilterBefore(buildApiTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class) 102 | ; 103 | } 104 | 105 | @Override 106 | protected void configure(AuthenticationManagerBuilder auth) { 107 | auth.authenticationProvider(securityAuthenticationProvider); 108 | } 109 | 110 | @Bean 111 | public PersistentTokenBasedRememberMeServices persistentTokenBasedRememberMeServices() { 112 | PersistentTokenBasedRememberMeServices persistenceTokenBasedservice = new PersistentTokenBasedRememberMeServices( 113 | rememberKey, userDetailsService, persistenceTokenRepository); 114 | return persistenceTokenBasedservice; 115 | } 116 | 117 | protected SecurityUserLoginProcessingFilter buildWebSecurityUserLoginProcessingFilter() throws Exception { 118 | SecurityUserLoginProcessingFilter filter = new SecurityUserLoginProcessingFilter("/loginProcess", 119 | securityUserLoginHandler, persistentTokenBasedRememberMeServices()); 120 | filter.setAuthenticationManager(authenticationManager); 121 | return filter; 122 | } 123 | 124 | protected SecurityUserLoginProcessingFilter buildApiSecurityUserLoginProcessingFilter() throws Exception { 125 | SecurityUserLoginProcessingFilter filter = new SecurityUserLoginProcessingFilter("/api/login", 126 | securityUserLoginHandler); 127 | filter.setAuthenticationManager(authenticationManager); 128 | return filter; 129 | } 130 | 131 | protected ApiTokenAuthenticationProcessingFilter buildApiTokenAuthenticationProcessingFilter() throws Exception { 132 | SkipMatcher skipMatcher = new SkipMatcher("/api/**"); 133 | ApiTokenAuthenticationProcessingFilter filter = new ApiTokenAuthenticationProcessingFilter(skipMatcher, 134 | userRepository, rolePermissionRepository, securityUserLoginHandler, apiTokenFactory); 135 | filter.setAuthenticationManager(this.authenticationManager); 136 | return filter; 137 | } 138 | 139 | public static class SkipMatcher implements RequestMatcher { 140 | 141 | private OrRequestMatcher skipRequestMatcher; 142 | 143 | private AntPathRequestMatcher antPathRequestMatcher; 144 | 145 | public SkipMatcher(String processUrl) { 146 | skipRequestMatcher = new OrRequestMatcher( 147 | Arrays.asList(new AntPathRequestMatcher("/api/login"), new AntPathRequestMatcher("/api/token"))); 148 | antPathRequestMatcher = new AntPathRequestMatcher(processUrl); 149 | } 150 | 151 | @Override 152 | public boolean matches(HttpServletRequest request) { 153 | if (skipRequestMatcher.matches(request)) { 154 | return false; 155 | } 156 | return antPathRequestMatcher.matches(request); 157 | } 158 | } 159 | 160 | @Bean 161 | public PasswordEncoder encoder() { 162 | return new BCryptPasswordEncoder(11); 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/security/api/filter/ApiTokenAuthenticationProcessingFilter.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.security.api.filter; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.stream.Collectors; 8 | 9 | import javax.servlet.FilterChain; 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | 14 | import com.okihouse.repository.RolePermissionRepository; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | import org.springframework.security.authentication.BadCredentialsException; 18 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 19 | import org.springframework.security.core.Authentication; 20 | import org.springframework.security.core.AuthenticationException; 21 | import org.springframework.security.core.GrantedAuthority; 22 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 23 | import org.springframework.security.core.context.SecurityContext; 24 | import org.springframework.security.core.context.SecurityContextHolder; 25 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 26 | import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; 27 | 28 | import com.okihouse.entity.User; 29 | import com.okihouse.entity.Role; 30 | import com.okihouse.repository.UserRepository; 31 | import com.okihouse.security.Security; 32 | import com.okihouse.security.api.token.ApiTokenFactory; 33 | import com.okihouse.security.api.token.data.ApiTokenData; 34 | import com.okihouse.security.common.handler.SecurityUserLoginHandler; 35 | 36 | /** 37 | * Created by okihouse16 on 2017. 12. 21.. 38 | */ 39 | public class ApiTokenAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter { 40 | 41 | private static final Logger logger = LoggerFactory.getLogger(ApiTokenAuthenticationProcessingFilter.class); 42 | 43 | public ApiTokenAuthenticationProcessingFilter( 44 | Security.SkipMatcher skipMatcher 45 | ,UserRepository userRepository 46 | ,RolePermissionRepository rolePermissionRepository 47 | ,SecurityUserLoginHandler securityUserLoginHandler 48 | ,ApiTokenFactory apiTokenFactory) { 49 | super(skipMatcher); 50 | this.userRepository = userRepository; 51 | this.rolePermissionRepository = rolePermissionRepository; 52 | this.securityUserLoginHandler = securityUserLoginHandler; 53 | this.apiTokenFactory = apiTokenFactory; 54 | } 55 | 56 | private UserRepository userRepository; 57 | 58 | private RolePermissionRepository rolePermissionRepository; 59 | 60 | private SecurityUserLoginHandler securityUserLoginHandler; 61 | 62 | private ApiTokenFactory apiTokenFactory; 63 | 64 | @Override 65 | public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { 66 | String accessToken = apiTokenFactory.extract(request.getHeader(ApiTokenData.AUTHORIZATION_HEADER_NAME)); 67 | Map body = apiTokenFactory.getBody(accessToken); 68 | String principal = (String) body.get("sub"); // username 69 | 70 | /* 71 | * Do not use granted authority roles in token. (Must use the data that is stored in database or other storage.) 72 | * Granted authority can be changed by administrator at any time. 73 | */ 74 | User user = userRepository.findFirstByUsername(principal); 75 | if(user == null){ 76 | throw new UsernameNotFoundException("User is not exist!"); 77 | } 78 | 79 | // Use the authorities of the user saved in database. 80 | List roles = user.getRoles(); 81 | if (roles.isEmpty()) { 82 | throw new BadCredentialsException("Authentication Failed. User granted authority is empty."); 83 | } 84 | 85 | List roleIds = roles.stream() 86 | .map(Role::getId) 87 | .collect(Collectors.toList()); 88 | 89 | List permissions = rolePermissionRepository.permissions(roleIds); 90 | 91 | List grantedAuthorities = new ArrayList<>(); 92 | permissions.stream() 93 | .forEach(p -> grantedAuthorities.add(new SimpleGrantedAuthority(p))); 94 | 95 | logger.info("Api user attempt authentication. username={}, grantedAuthorities={}", principal, grantedAuthorities); 96 | return new UsernamePasswordAuthenticationToken(principal, null, grantedAuthorities); // No required credentials. 97 | } 98 | 99 | @Override 100 | protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { 101 | SecurityContext context = SecurityContextHolder.createEmptyContext(); 102 | context.setAuthentication(authResult); 103 | SecurityContextHolder.setContext(context); 104 | chain.doFilter(request, response); 105 | } 106 | 107 | @Override 108 | protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { 109 | SecurityContextHolder.clearContext(); 110 | securityUserLoginHandler.onAuthenticationFailure(request, response, failed); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/security/api/token/ApiRefreshTokenController.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.security.api.token; 2 | 3 | import com.okihouse.security.api.token.data.ApiTokenData; 4 | import io.jsonwebtoken.lang.Assert; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.HttpHeaders; 9 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 10 | import org.springframework.security.core.Authentication; 11 | import org.springframework.security.core.GrantedAuthority; 12 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 13 | import org.springframework.web.bind.annotation.*; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | /** 20 | * Created by okihouse16 on 2017. 12. 22.. 21 | */ 22 | @RestController 23 | public class ApiRefreshTokenController { 24 | 25 | @Autowired 26 | private ApiTokenFactory apiTokenFactory; 27 | 28 | @RequestMapping(value = "/api/token", method = RequestMethod.GET) 29 | @ResponseBody 30 | public RefreshTokenData token(@RequestHeader(HttpHeaders.AUTHORIZATION) String token) { 31 | String accessToken = apiTokenFactory.extract(token); 32 | Assert.hasLength(accessToken, "Request token is invalid."); 33 | 34 | Map body = apiTokenFactory.getBody(accessToken); 35 | @SuppressWarnings("unchecked") 36 | List scopes = (List) body.get("scopes"); 37 | if(scopes.isEmpty() || !scopes.contains(ApiTokenData.AUTHORIZATION_REFRESH_TOKEN_NAME)) { 38 | throw new IllegalArgumentException("Request token is not 'REFRESH_TOKEN'."); 39 | } 40 | scopes.remove(ApiTokenData.AUTHORIZATION_REFRESH_TOKEN_NAME); 41 | 42 | List grantedAuthorities = new ArrayList<>(); 43 | scopes.stream() 44 | .forEach(r -> grantedAuthorities.add(new SimpleGrantedAuthority(r.toString()))); 45 | 46 | String username = (String) body.get("sub"); 47 | 48 | Authentication authentication = new UsernamePasswordAuthenticationToken(username, null, grantedAuthorities); 49 | String newAccessToken = apiTokenFactory.createToken(authentication); 50 | String newRefreshAccessToken = apiTokenFactory.createRefreshToken(authentication); 51 | return new RefreshTokenData(newAccessToken, newRefreshAccessToken); 52 | } 53 | 54 | @Data 55 | @AllArgsConstructor 56 | public static class RefreshTokenData { 57 | 58 | private String newAccessToken; 59 | 60 | private String newRefreshAccessToken; 61 | 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/security/api/token/ApiTokenFactory.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.security.api.token; 2 | 3 | import com.okihouse.security.api.token.data.ApiTokenData; 4 | import io.jsonwebtoken.*; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.security.authentication.AuthenticationServiceException; 10 | import org.springframework.security.authentication.BadCredentialsException; 11 | import org.springframework.security.core.Authentication; 12 | import org.springframework.security.core.GrantedAuthority; 13 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 14 | import org.springframework.stereotype.Component; 15 | 16 | import java.time.LocalDateTime; 17 | import java.time.ZoneId; 18 | import java.util.*; 19 | import java.util.stream.Collectors; 20 | 21 | /** 22 | * Created by okihouse16 on 2017. 12. 21.. 23 | */ 24 | @Component 25 | public class ApiTokenFactory { 26 | 27 | private static final Logger logger = LoggerFactory.getLogger(ApiTokenFactory.class); 28 | 29 | @Autowired 30 | private ApiTokenData apiTokenData; 31 | 32 | public String createToken(Authentication authentication) { 33 | String username = authentication.getName(); 34 | if (StringUtils.isBlank(username)) { 35 | throw new UsernameNotFoundException("Can't create token without username"); 36 | } 37 | 38 | @SuppressWarnings("unchecked") 39 | List grantedAuthorities = (List) authentication.getAuthorities(); 40 | if (grantedAuthorities == null || grantedAuthorities.isEmpty()) { 41 | throw new BadCredentialsException("User doesn't have any privileges"); 42 | } 43 | 44 | Claims claims = Jwts.claims().setSubject(username); 45 | claims.put("scopes", grantedAuthorities.stream().map(s -> s.toString()).collect(Collectors.toList())); 46 | 47 | LocalDateTime currentTime = LocalDateTime.now(); 48 | 49 | String token = Jwts.builder() 50 | .setClaims(claims) 51 | .setIssuer(apiTokenData.getIssuer()) 52 | .setIssuedAt(Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant())) 53 | .setExpiration(Date.from(currentTime 54 | .plusMinutes(apiTokenData.getExpired()) 55 | .atZone(ZoneId.systemDefault()).toInstant())) 56 | .signWith(SignatureAlgorithm.HS512, apiTokenData.getSigningKey()) 57 | .compact(); 58 | return token; 59 | } 60 | 61 | public String createRefreshToken(Authentication authentication) { 62 | String username = authentication.getName(); 63 | if (StringUtils.isBlank(username)) { 64 | throw new UsernameNotFoundException("Can't create token without username"); 65 | } 66 | 67 | @SuppressWarnings("unchecked") 68 | List grantedAuthorities = (List) authentication.getAuthorities(); 69 | if (grantedAuthorities == null || grantedAuthorities.isEmpty()) { 70 | throw new BadCredentialsException("User doesn't have any privileges"); 71 | } 72 | 73 | Claims claims = Jwts.claims().setSubject(username); 74 | List scopes = grantedAuthorities.stream().map(s -> s.toString()).collect(Collectors.toList()); 75 | scopes.add(ApiTokenData.AUTHORIZATION_REFRESH_TOKEN_NAME); 76 | claims.put("scopes", scopes); 77 | 78 | LocalDateTime currentTime = LocalDateTime.now(); 79 | 80 | String token = Jwts.builder() 81 | .setClaims(claims) 82 | .setIssuer(apiTokenData.getIssuer()) 83 | .setId(UUID.randomUUID().toString()) 84 | .setIssuedAt(Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant())) 85 | .setExpiration(Date.from(currentTime 86 | .plusMinutes(apiTokenData.getRefreshExpired()) 87 | .atZone(ZoneId.systemDefault()).toInstant())) 88 | .signWith(SignatureAlgorithm.HS512, apiTokenData.getSigningKey()) 89 | .compact(); 90 | return token; 91 | } 92 | 93 | public Map getBody(String token){ 94 | return parseClaims(token).getBody(); 95 | } 96 | 97 | public String extract(String header) { 98 | if (StringUtils.isBlank(header)) { 99 | throw new AuthenticationServiceException("Authorization header cannot be blank!"); 100 | } 101 | 102 | if (header.length() <= ApiTokenData.AUTHORIZATION_HEADER_PREFIX.length()) { 103 | throw new AuthenticationServiceException("Invalid authorization header size."); 104 | } 105 | return header.substring(ApiTokenData.AUTHORIZATION_HEADER_PREFIX.length(), header.length()); 106 | } 107 | 108 | private Jws parseClaims(String token) { 109 | try { 110 | return Jwts.parser() 111 | .setSigningKey(apiTokenData.getSigningKey()) 112 | .parseClaimsJws(token); 113 | } catch (ExpiredJwtException e) { 114 | logger.warn("Request token is expired. error={}", e.getMessage()); 115 | throw new BadCredentialsException("Request token is expired."); 116 | } catch (Exception e) { 117 | logger.error("Request token is invalid. token={}, error={}", token, e.getMessage()); 118 | throw new BadCredentialsException("Request token is invalid."); 119 | } 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/security/api/token/data/ApiTokenData.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.security.api.token.data; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | * Created by okihouse16 on 2017. 12. 22.. 9 | */ 10 | @Data 11 | @Configuration 12 | @ConfigurationProperties(prefix="security.token") 13 | public class ApiTokenData { 14 | 15 | private String issuer; 16 | 17 | private Long expired; 18 | 19 | private Long refreshExpired; 20 | 21 | private String signingKey; 22 | 23 | public static final String AUTHORIZATION_REFRESH_TOKEN_NAME = "REFRESH_TOKEN"; 24 | 25 | public static final String AUTHORIZATION_HEADER_NAME = "Authorization"; 26 | 27 | public static final String AUTHORIZATION_HEADER_PREFIX = "Bearer "; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/security/common/data/SecurityUrlData.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.security.common.data; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | import lombok.Data; 7 | 8 | @Data 9 | @Configuration 10 | @ConfigurationProperties(prefix="security.url") 11 | public class SecurityUrlData { 12 | 13 | private String login; 14 | 15 | private String logout; 16 | 17 | private String mypage; 18 | 19 | private String accessdenied; 20 | 21 | private String refreshToken; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/security/common/details/UserDetailsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.security.common.details; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.stream.Collectors; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.authentication.BadCredentialsException; 9 | import org.springframework.security.core.GrantedAuthority; 10 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 11 | import org.springframework.security.core.userdetails.UserDetails; 12 | import org.springframework.security.core.userdetails.UserDetailsService; 13 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 14 | import org.springframework.stereotype.Component; 15 | 16 | import com.okihouse.entity.Role; 17 | import com.okihouse.entity.User; 18 | import com.okihouse.repository.RolePermissionRepository; 19 | import com.okihouse.repository.UserRepository; 20 | 21 | @Component 22 | public class UserDetailsServiceImpl implements UserDetailsService { 23 | 24 | @Autowired 25 | private UserRepository userRepository; 26 | 27 | @Autowired 28 | private RolePermissionRepository rolePermissionRepository; 29 | 30 | /* 31 | * https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#remember-me 32 | * UserDetailsService is required for Remember-Me service. 33 | */ 34 | @Override 35 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 36 | // Find user in your database by username. 37 | User user = userRepository.findFirstByUsername(username); 38 | if(user == null){ 39 | throw new UsernameNotFoundException("User is not exist!"); 40 | } 41 | 42 | String password = user.getPassword(); // Use the password of the user saved in database. 43 | 44 | // Use the authorities of the user saved in database. 45 | List roles = user.getRoles(); 46 | if (roles.isEmpty()) { 47 | throw new BadCredentialsException("Authentication Failed. User granted authority is empty."); 48 | } 49 | 50 | List roleIds = roles.stream() 51 | .map(Role::getId) 52 | .collect(Collectors.toList()); 53 | 54 | List permissions = rolePermissionRepository.permissions(roleIds); 55 | 56 | List grantedAuthorities = new ArrayList<>(); 57 | permissions.stream() 58 | .forEach(p -> grantedAuthorities.add(new SimpleGrantedAuthority(p))); 59 | return new org.springframework.security.core.userdetails.User(username, password, grantedAuthorities); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/security/common/entrypoint/SecurityUserAccessEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.security.common.entrypoint; 2 | 3 | import java.io.IOException; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import javax.servlet.ServletException; 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletResponse; 10 | 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.http.MediaType; 14 | import org.springframework.security.core.AuthenticationException; 15 | import org.springframework.security.web.AuthenticationEntryPoint; 16 | import org.springframework.stereotype.Service; 17 | 18 | import com.okihouse.security.common.data.SecurityUrlData; 19 | import com.okihouse.util.AjaxUtils; 20 | import com.okihouse.util.JsonUtils; 21 | 22 | @Service 23 | public class SecurityUserAccessEntryPoint implements AuthenticationEntryPoint { 24 | 25 | @Autowired 26 | private SecurityUrlData urlInformation; 27 | 28 | @Override 29 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) 30 | throws IOException, ServletException { 31 | if(AjaxUtils.isAjax(request) || AjaxUtils.isApi(request)) { 32 | Map map = new HashMap<>(); 33 | map.put("success", false); 34 | map.put("redirect", urlInformation.getLogin()); 35 | 36 | response.setStatus(HttpStatus.UNAUTHORIZED.value()); 37 | response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); 38 | response.getWriter().write(JsonUtils.toJson(map)); 39 | response.getWriter().flush(); 40 | response.getWriter().close(); 41 | } else { 42 | response.sendRedirect(request.getContextPath() + urlInformation.getLogin()); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/security/common/filter/SecurityUserLoginProcessingFilter.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.security.common.filter; 2 | 3 | import java.io.IOException; 4 | import java.util.Map; 5 | 6 | import javax.servlet.FilterChain; 7 | import javax.servlet.ServletException; 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletResponse; 10 | 11 | import com.okihouse.security.common.handler.SecurityUserLoginHandler; 12 | import com.okihouse.util.AjaxUtils; 13 | import org.apache.commons.lang3.StringUtils; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.http.HttpMethod; 17 | import org.springframework.security.authentication.AuthenticationServiceException; 18 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 19 | import org.springframework.security.core.Authentication; 20 | import org.springframework.security.core.AuthenticationException; 21 | import org.springframework.security.core.context.SecurityContextHolder; 22 | import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; 23 | import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices; 24 | 25 | import com.okihouse.util.JsonUtils; 26 | import com.fasterxml.jackson.core.type.TypeReference; 27 | 28 | /** 29 | * Created by okihouse16 on 2017. 12. 21.. 30 | */ 31 | public class SecurityUserLoginProcessingFilter extends AbstractAuthenticationProcessingFilter { 32 | 33 | private static Logger logger = LoggerFactory.getLogger(SecurityUserLoginProcessingFilter.class); 34 | 35 | public SecurityUserLoginProcessingFilter ( 36 | String processUrl 37 | ,SecurityUserLoginHandler securityUserLoginHandler 38 | ,PersistentTokenBasedRememberMeServices rememberMeServices) { 39 | super(processUrl); 40 | this.securityUserLoginHandler = securityUserLoginHandler; 41 | this.rememberMeServices = rememberMeServices; 42 | } 43 | 44 | public SecurityUserLoginProcessingFilter ( 45 | String processUrl 46 | ,SecurityUserLoginHandler securityUserLoginHandler) { 47 | super(processUrl); 48 | this.securityUserLoginHandler = securityUserLoginHandler; 49 | } 50 | 51 | private boolean isRememberMe = false; 52 | 53 | private SecurityUserLoginHandler securityUserLoginHandler; 54 | 55 | private PersistentTokenBasedRememberMeServices rememberMeServices; 56 | 57 | @Override 58 | public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) 59 | throws AuthenticationException, IOException, ServletException { 60 | if (!HttpMethod.POST.name().equals(request.getMethod())) { 61 | if(logger.isDebugEnabled()) { 62 | logger.debug("Authentication method not supported. Request method: " + request.getMethod()); 63 | } 64 | throw new AuthenticationServiceException("Authentication method not supported"); 65 | } 66 | 67 | String username = request.getParameter("username"); 68 | String password = request.getParameter("password"); 69 | String rememberMe = request.getParameter("remember-me"); 70 | 71 | if(AjaxUtils.isApi(request)){ 72 | Map userContext = JsonUtils.fromJson(request.getReader(), new TypeReference>(){}); 73 | username = userContext.get("username"); 74 | password = userContext.get("password"); 75 | } 76 | 77 | if(StringUtils.isNotBlank(rememberMe)){ 78 | this.isRememberMe = true; 79 | } 80 | 81 | if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { 82 | throw new AuthenticationServiceException("username or password is not valid."); 83 | } 84 | UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password); 85 | logger.info("User attempt authentication. username={}", username); 86 | return this.getAuthenticationManager().authenticate(usernamePasswordAuthenticationToken); 87 | } 88 | 89 | @Override 90 | protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, 91 | Authentication authentication) throws IOException, ServletException { 92 | SecurityContextHolder.getContext().setAuthentication(authentication); 93 | if (this.isRememberMe) { 94 | if(logger.isDebugEnabled()) { 95 | logger.debug("User remember-me service is activated."); 96 | } 97 | rememberMeServices.loginSuccess(request, response, authentication); 98 | } 99 | securityUserLoginHandler.onAuthenticationSuccess(request, response, authentication); 100 | } 101 | 102 | @Override 103 | protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, 104 | AuthenticationException failed) throws IOException, ServletException { 105 | SecurityContextHolder.clearContext(); 106 | securityUserLoginHandler.onAuthenticationFailure(request, response, failed); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/security/common/handler/SecurityUserAcessDeniedHandler.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.security.common.handler; 2 | 3 | import com.okihouse.security.common.data.SecurityUrlData; 4 | import com.okihouse.util.AjaxUtils; 5 | import com.okihouse.util.JsonUtils; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.security.access.AccessDeniedException; 11 | import org.springframework.security.web.access.AccessDeniedHandler; 12 | 13 | import javax.servlet.ServletException; 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import java.io.IOException; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | /** 21 | * Created by okihouse16 on 2017. 12. 21.. 22 | */ 23 | @Configuration 24 | public class SecurityUserAcessDeniedHandler implements AccessDeniedHandler { 25 | 26 | @Autowired 27 | private SecurityUrlData securityUrlData; 28 | 29 | @Override 30 | public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { 31 | if (AjaxUtils.isAjax(request) || AjaxUtils.isApi(request)) { 32 | Map map = new HashMap<>(); 33 | map.put("success", false); 34 | map.put("error", accessDeniedException.getMessage()); 35 | 36 | response.setStatus(HttpStatus.FORBIDDEN.value()); 37 | response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); 38 | response.getWriter().write(JsonUtils.toJson(map)); 39 | response.getWriter().flush(); 40 | } else { 41 | response.sendRedirect(securityUrlData.getAccessdenied()); 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/security/common/handler/SecurityUserLoginHandler.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.security.common.handler; 2 | 3 | import com.okihouse.security.api.token.ApiTokenFactory; 4 | import com.okihouse.security.common.data.SecurityUrlData; 5 | import com.okihouse.util.AjaxUtils; 6 | import com.okihouse.util.JsonUtils; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.MediaType; 11 | import org.springframework.security.core.Authentication; 12 | import org.springframework.security.core.AuthenticationException; 13 | import org.springframework.security.core.context.SecurityContextHolder; 14 | import org.springframework.security.web.authentication.AuthenticationFailureHandler; 15 | import org.springframework.security.web.authentication.AuthenticationSuccessHandler; 16 | 17 | import javax.servlet.ServletException; 18 | import javax.servlet.http.HttpServletRequest; 19 | import javax.servlet.http.HttpServletResponse; 20 | import java.io.IOException; 21 | import java.net.URLEncoder; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | 25 | @Configuration 26 | public class SecurityUserLoginHandler implements AuthenticationSuccessHandler, AuthenticationFailureHandler { 27 | 28 | @Autowired 29 | private ApiTokenFactory tokenFactory; 30 | 31 | @Autowired 32 | private SecurityUrlData securityUrlData; 33 | 34 | @Override 35 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { 36 | if (AjaxUtils.isAjax(request) || AjaxUtils.isApi(request)) { 37 | Map map = new HashMap<>(); 38 | map.put("success", true); 39 | if(AjaxUtils.isAjax(request)){ 40 | map.put("redirect", securityUrlData.getMypage()); 41 | } else { 42 | map.put("token", tokenFactory.createToken(authentication)); 43 | map.put("refreshToken", tokenFactory.createRefreshToken(authentication)); 44 | SecurityContextHolder.clearContext(); 45 | } 46 | 47 | response.setStatus(HttpStatus.OK.value()); 48 | response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); 49 | response.getWriter().write(JsonUtils.toJson(map)); 50 | response.getWriter().flush(); 51 | } else { 52 | response.sendRedirect(securityUrlData.getMypage()); 53 | } 54 | } 55 | 56 | @Override 57 | public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { 58 | if (AjaxUtils.isAjax(request) || AjaxUtils.isApi(request)) { 59 | Map map = new HashMap<>(); 60 | map.put("success", false); 61 | map.put("error", exception.getMessage()); 62 | 63 | response.setStatus(HttpStatus.UNAUTHORIZED.value()); 64 | response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); 65 | response.getWriter().write(JsonUtils.toJson(map)); 66 | response.getWriter().flush(); 67 | } else { 68 | SecurityContextHolder.clearContext(); 69 | String query = String.format("?error=%s", URLEncoder.encode(exception.getMessage(), "utf-8")); 70 | response.sendRedirect(securityUrlData.getLogin() + query); 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/security/common/provider/SecurityAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.security.common.provider; 2 | 3 | import com.okihouse.entity.Role; 4 | import com.okihouse.entity.User; 5 | import com.okihouse.repository.RolePermissionRepository; 6 | import com.okihouse.repository.UserRepository; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.security.authentication.AuthenticationProvider; 11 | import org.springframework.security.authentication.AuthenticationServiceException; 12 | import org.springframework.security.authentication.BadCredentialsException; 13 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 14 | import org.springframework.security.core.Authentication; 15 | import org.springframework.security.core.AuthenticationException; 16 | import org.springframework.security.core.GrantedAuthority; 17 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 18 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 19 | import org.springframework.security.crypto.password.PasswordEncoder; 20 | import org.springframework.stereotype.Component; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import java.util.stream.Collectors; 25 | 26 | /** 27 | * Created by okihouse16 on 2017. 12. 21.. 28 | */ 29 | @Component 30 | public class SecurityAuthenticationProvider implements AuthenticationProvider { 31 | 32 | private static final Logger logger = LoggerFactory.getLogger(SecurityAuthenticationProvider.class); 33 | 34 | @Autowired 35 | private UserRepository userRepository; 36 | 37 | @Autowired 38 | private RolePermissionRepository rolePermissionRepository; 39 | 40 | @Autowired 41 | private PasswordEncoder encoder; 42 | 43 | @Override 44 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 45 | if(authentication == null){ 46 | if(logger.isDebugEnabled()) { 47 | logger.debug("Authentication is null"); 48 | } 49 | throw new AuthenticationServiceException("No authentication data provided"); 50 | } 51 | 52 | String username = authentication.getName(); 53 | Object credentials = authentication.getCredentials(); 54 | if (!(credentials instanceof String)) { 55 | throw new AuthenticationServiceException("Authentication credentials is not valid"); 56 | } 57 | String password = credentials.toString(); 58 | 59 | // Find user in your database by username. 60 | User user = userRepository.findFirstByUsername(username); 61 | if(user == null){ 62 | throw new UsernameNotFoundException("User is not exist!"); 63 | } 64 | 65 | String savedUserPassword = user.getPassword(); // Use the password of the user saved in database. 66 | if (!encoder.matches(password, savedUserPassword)) { 67 | throw new BadCredentialsException("Authentication Failed. Username or Password not valid."); 68 | } 69 | 70 | // Use the authorities of the user saved in database. 71 | List roles = user.getRoles(); 72 | if (roles.isEmpty()) { 73 | throw new BadCredentialsException("Authentication Failed. User granted authority is empty."); 74 | } 75 | 76 | List roleIds = roles.stream() 77 | .map(Role::getId) 78 | .collect(Collectors.toList()); 79 | 80 | List permissions = rolePermissionRepository.permissions(roleIds); 81 | 82 | List grantedAuthorities = new ArrayList<>(); 83 | permissions.stream() 84 | .forEach(p -> grantedAuthorities.add(new SimpleGrantedAuthority(p))); 85 | 86 | return new UsernamePasswordAuthenticationToken(username, password, grantedAuthorities); 87 | } 88 | 89 | @Override 90 | public boolean supports(Class authentication) { 91 | return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/security/common/repository/PersistTokenRepository.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.security.common.repository; 2 | 3 | import com.okihouse.security.common.repository.vo.RememberToken; 4 | import com.okihouse.util.JsonUtils; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.data.redis.core.StringRedisTemplate; 9 | import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken; 10 | import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.Date; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | @Service 17 | public class PersistTokenRepository implements PersistentTokenRepository { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(PersistTokenRepository.class); 20 | 21 | @Autowired 22 | private StringRedisTemplate stringRedisTemplate; 23 | 24 | @Override 25 | public void createNewToken(PersistentRememberMeToken token) { 26 | RememberToken rememberToken = 27 | new RememberToken(token.getUsername(), token.getSeries(), token.getTokenValue(), token.getDate()); 28 | stringRedisTemplate.opsForValue().set(token.getSeries(), JsonUtils.toJson(rememberToken), 30, TimeUnit.DAYS); 29 | } 30 | 31 | @Override 32 | public void updateToken(String series, String tokenValue, Date lastUsed) { 33 | String payload = stringRedisTemplate.opsForValue().get(series); 34 | try { 35 | RememberToken rememberToken = JsonUtils.fromJson(payload, RememberToken.class); 36 | rememberToken.setTokenValue(tokenValue); 37 | rememberToken.setDate(lastUsed); 38 | stringRedisTemplate.opsForValue().set(series, JsonUtils.toJson(rememberToken), 30, TimeUnit.DAYS); 39 | logger.debug("Remember me token is updated. seried={}", series); 40 | } catch (Exception e) { 41 | logger.error("Persistent token is not valid. payload={}, error={}", payload, e); 42 | } 43 | } 44 | 45 | @Override 46 | public PersistentRememberMeToken getTokenForSeries(String seriesId) { 47 | String payload = stringRedisTemplate.opsForValue().get(seriesId); 48 | if (payload == null) return null; 49 | try { 50 | RememberToken rememberToken = JsonUtils.fromJson(payload, RememberToken.class); 51 | PersistentRememberMeToken token = new PersistentRememberMeToken( 52 | rememberToken.getUsername() 53 | , seriesId 54 | , rememberToken.getTokenValue() 55 | , rememberToken.getDate()); 56 | return token; 57 | } catch (Exception e) { 58 | logger.error("Persistent token is not valid. payload={}, error={}", payload, e); 59 | return null; 60 | } 61 | } 62 | 63 | @Override 64 | public void removeUserTokens(String username) { 65 | // Skip this scenario, because redis set only unique key. 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/security/common/repository/vo/RememberToken.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.security.common.repository.vo; 2 | 3 | import java.util.Date; 4 | 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class RememberToken { 13 | 14 | private String username; 15 | 16 | private String series; 17 | 18 | private String tokenValue; 19 | 20 | private Date date; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/security/web/handler/WebSecurityUserLogoutHandler.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.security.web.handler; 2 | 3 | import java.io.IOException; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import javax.servlet.ServletException; 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletResponse; 10 | 11 | import com.okihouse.security.common.data.SecurityUrlData; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.http.MediaType; 14 | import org.springframework.security.core.Authentication; 15 | import org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler; 16 | import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; 17 | import org.springframework.stereotype.Service; 18 | 19 | import com.okihouse.util.AjaxUtils; 20 | import com.okihouse.util.JsonUtils; 21 | 22 | @Service 23 | public class WebSecurityUserLogoutHandler extends AbstractAuthenticationTargetUrlRequestHandler implements LogoutSuccessHandler { 24 | 25 | @Autowired 26 | private SecurityUrlData urlInformation; 27 | 28 | @Override 29 | public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) 30 | throws IOException, ServletException { 31 | if(AjaxUtils.isAjax(request)) { 32 | Map map = new HashMap<>(); 33 | map.put("success", true); 34 | map.put("redirect", urlInformation.getLogin()); 35 | 36 | response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); 37 | response.getWriter().write(JsonUtils.toJson(map)); 38 | response.getWriter().flush(); 39 | response.getWriter().close(); 40 | } else { 41 | super.handle(request, response, authentication); 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/util/AjaxUtils.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.util; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | public class AjaxUtils { 8 | 9 | public static boolean isAjax(HttpServletRequest request) { 10 | String accept = request.getHeader("accept"); 11 | String ajax = request.getHeader("X-Requested-With"); 12 | return (StringUtils.indexOf(accept, "json") > -1 && StringUtils.isNotEmpty(ajax)); 13 | } 14 | 15 | public static boolean isApi(HttpServletRequest request) { 16 | String accept = request.getHeader("accept"); 17 | String ajax = request.getHeader("X-Requested-With"); 18 | return (StringUtils.indexOf(accept, "json") > -1 && StringUtils.isEmpty(ajax)); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/util/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.util; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import com.fasterxml.jackson.annotation.JsonInclude; 7 | import com.fasterxml.jackson.core.JsonParser; 8 | import com.fasterxml.jackson.core.JsonProcessingException; 9 | import com.fasterxml.jackson.core.type.TypeReference; 10 | import com.fasterxml.jackson.databind.DeserializationFeature; 11 | import com.fasterxml.jackson.databind.MapperFeature; 12 | import com.fasterxml.jackson.databind.ObjectMapper; 13 | import com.fasterxml.jackson.databind.SerializationFeature; 14 | 15 | import java.io.BufferedReader; 16 | 17 | public class JsonUtils { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class); 20 | 21 | private final ObjectMapper objectMapper; 22 | 23 | private JsonUtils() { 24 | objectMapper = new ObjectMapper(); 25 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 26 | objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); 27 | objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 28 | objectMapper.configure(MapperFeature.AUTO_DETECT_GETTERS, true); 29 | objectMapper.configure(MapperFeature.AUTO_DETECT_IS_GETTERS, true); 30 | objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); 31 | objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false); 32 | } 33 | 34 | public static JsonUtils getInstance() { 35 | return new JsonUtils(); 36 | } 37 | 38 | private static ObjectMapper getMapper() { 39 | return getInstance().objectMapper; 40 | } 41 | 42 | public static String toJson(Object value) { 43 | try { 44 | return getMapper().writeValueAsString(value); 45 | } catch (Exception e) { 46 | throw new RuntimeException(e); 47 | } 48 | } 49 | 50 | public static byte[] toJsonAsByte(Object value) { 51 | try { 52 | return getMapper().writeValueAsBytes(value); 53 | } catch (Exception e) { 54 | throw new RuntimeException(e); 55 | } 56 | } 57 | 58 | public static T fromJson(byte[] json, Class cls) { 59 | try { 60 | return getMapper().readValue(json, cls); 61 | } catch (Exception e) { 62 | throw new RuntimeException(e); 63 | } 64 | } 65 | 66 | public static T fromJson(String json, Class cls) { 67 | try { 68 | return getMapper().readValue(json, cls); 69 | } catch (Exception e) { 70 | logger.warn("Json parsing is failed. origin_json={}", json); 71 | throw new RuntimeException(e); 72 | } 73 | } 74 | public static T fromJson(BufferedReader json, TypeReference typeReference) { 75 | try { 76 | return getMapper().readValue(json, typeReference); 77 | } catch (Exception e) { 78 | logger.warn("Json parsing is failed. origin_json={}", json); 79 | throw new RuntimeException(e); 80 | } 81 | } 82 | 83 | public static T fromJson(String json, TypeReference typeReference) { 84 | try { 85 | return getMapper().readValue(json, typeReference); 86 | } catch (Exception e) { 87 | logger.warn("Json parsing is failed. origin_json={}", json); 88 | throw new RuntimeException(e); 89 | } 90 | } 91 | 92 | public static String toPrettyJson(String value) { 93 | Object jsonObject = JsonUtils.fromJson(value, Object.class); 94 | try { 95 | return getMapper().writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject); 96 | } catch (JsonProcessingException e) { 97 | e.printStackTrace(); 98 | return ""; 99 | } 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/okihouse/vo/SuccessVO.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.vo; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class SuccessVO { 7 | 8 | private int code = 0; 9 | private String message = "success"; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/additional-spring-configuration-metadata.json: -------------------------------------------------------------------------------- 1 | {"properties": [ 2 | { 3 | "name": "security.url.login", 4 | "type": "java.lang.String", 5 | "description": "security url for login" 6 | }, 7 | { 8 | "name": "security.url.logout", 9 | "type": "java.lang.String", 10 | "description": "security url for logout" 11 | }, 12 | { 13 | "name": "security.url.mypage", 14 | "type": "java.lang.String", 15 | "description": "security url for user mypage" 16 | } 17 | ]} -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | logging: 2 | config: classpath:logback.xml 3 | 4 | server: 5 | port: 3000 6 | context-path: / 7 | error: 8 | whitelabel: 9 | enabled: true 10 | session: 11 | cookie: 12 | http-only: true 13 | tracking-modes: cookie 14 | 15 | spring: 16 | mvc: 17 | view: 18 | prefix: /WEB-INF/ 19 | suffix: .jsp 20 | redis: 21 | host: localhost 22 | port: 6379 23 | 24 | jpa: 25 | database: h2 26 | hibernate: 27 | naming: 28 | strategy: org.hibernate.cfg.ImprovedNamingStrategy 29 | ddl-auto: create 30 | generate-ddl: true 31 | properties: 32 | hibernate.format_sql: true 33 | show-sql: true 34 | 35 | datasource: 36 | driver-class-name: org.h2.Driver 37 | url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 38 | username: sa 39 | data: "classpath:user.sql" 40 | 41 | security: 42 | url: 43 | login: /login 44 | logout: /logout 45 | mypage: /mypage 46 | accessdenied: /error/accessdenied 47 | refreshToken: /api/token 48 | token: 49 | issuer: security 50 | expired: 1 51 | refreshExpired: 60 52 | signing-key: securityKey 53 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %-5level | %d | %t | %F:%L | %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/resources/user.sql: -------------------------------------------------------------------------------- 1 | insert into USER(id, username, password) values(1, 'user', '$2a$11$BMHAHEws4Sz/SL5hhfLR/e45nehO.3RgV7j0v9mGaB9sYvEVHJb.e'); 2 | insert into USER(id, username, password) values(2, 'admin', '$2a$11$BMHAHEws4Sz/SL5hhfLR/e45nehO.3RgV7j0v9mGaB9sYvEVHJb.e'); 3 | 4 | insert into ROLE(role_id, id, role) values(1, 1, 'USER'); 5 | insert into ROLE(role_id, id, role) values(2, 2, 'ADMIN'); 6 | 7 | insert into ROLE_PERMISSION(permission_id, role_id, permission) values(1, 1, 'READ_MYPAGE'); 8 | insert into ROLE_PERMISSION(permission_id, role_id, permission) values(2, 2, 'READ_MYPAGE'); 9 | insert into ROLE_PERMISSION(permission_id, role_id, permission) values(3, 2, 'READ_ADMIN'); 10 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/error/accessdenied.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" pageEncoding="UTF-8"%> 2 | <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 403 Forbidden 12 | 13 | 14 | 15 | 16 | 17 | 18 | 34 | 35 | 36 | 37 |
38 |

Access Denied

39 |
40 |
41 |
You don't have permission to access on this page
42 |
43 |
44 | 45 |
46 | Move to mypage 47 | LOGOUT 48 |
49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/login.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" pageEncoding="UTF-8"%> 2 | <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Login 12 | 13 | 14 | 15 | 16 | 118 | 119 | 120 |
121 |
122 | 123 |

124 | 139 |
140 |
141 | 142 | 190 | 191 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/user/admin.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" pageEncoding="UTF-8"%> 2 | <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Admin 12 | 13 | 14 | 15 | 16 | 31 | 32 | 33 | 34 |
35 |

ADMIN

36 |
37 |
38 |
authentication: ${authentication}
39 |
40 |
41 | 42 | 47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/user/mypage.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" pageEncoding="UTF-8"%> 2 | <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Mypage 12 | 13 | 14 | 15 | 16 | 17 | 18 | 34 | 35 | 36 | 37 |
38 |

MYPAGE

39 |
40 |
41 |
authentication: ${authentication}
42 |
Principal: ${principal}
43 |
JSESSIONID: ${sessionId}
44 |
Cookie: ${cookie}
45 |
46 |
47 | 48 | 53 |
54 | 55 | 56 | -------------------------------------------------------------------------------- /src/test/java/com/okihouse/AllTests.java: -------------------------------------------------------------------------------- 1 | package com.okihouse; 2 | 3 | import com.okihouse.api.ApiWebAppSecurityTests; 4 | import com.okihouse.web.WebFormSecurityTests; 5 | import org.junit.runner.RunWith; 6 | import org.junit.runners.Suite; 7 | 8 | @RunWith(Suite.class) 9 | @Suite.SuiteClasses({ 10 | ApiWebAppSecurityTests.class, 11 | WebFormSecurityTests.class 12 | }) 13 | public class AllTests { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/com/okihouse/api/ApiWebAppSecurityTests.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.api; 2 | 3 | import com.okihouse.security.api.token.ApiTokenFactory; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.http.HttpHeaders; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 13 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 14 | import org.springframework.test.context.ContextConfiguration; 15 | import org.springframework.test.context.junit4.SpringRunner; 16 | import org.springframework.test.context.web.WebAppConfiguration; 17 | import org.springframework.test.web.servlet.MockMvc; 18 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 19 | import org.springframework.web.context.WebApplicationContext; 20 | 21 | import java.util.Arrays; 22 | 23 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; 24 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 25 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 26 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 27 | 28 | 29 | @RunWith(SpringRunner.class) 30 | @SpringBootTest 31 | @WebAppConfiguration 32 | @ContextConfiguration 33 | public class ApiWebAppSecurityTests { 34 | 35 | @Autowired 36 | private WebApplicationContext context; 37 | 38 | @Autowired 39 | private ApiTokenFactory apiTokenFactory; 40 | 41 | private MockMvc mvc; 42 | 43 | private String userAccessToken; 44 | private String adminAccessToken; 45 | 46 | @Before 47 | public void setup() { 48 | mvc = MockMvcBuilders 49 | .webAppContextSetup(context) 50 | .apply(springSecurity()) 51 | .build(); 52 | 53 | userAccessToken = apiTokenFactory.createToken( 54 | new UsernamePasswordAuthenticationToken( 55 | "user", 56 | "password", 57 | Arrays.asList(new SimpleGrantedAuthority("READ_MYPAGE")))); 58 | 59 | adminAccessToken = apiTokenFactory.createToken( 60 | new UsernamePasswordAuthenticationToken( 61 | "admin", 62 | "password", 63 | Arrays.asList( 64 | new SimpleGrantedAuthority("READ_MYPAGE"), 65 | new SimpleGrantedAuthority("READ_ADMIN") 66 | ) 67 | )); 68 | } 69 | 70 | @Test 71 | public void user_test() throws Exception { 72 | mvc 73 | .perform(get("/api/mypage") 74 | .header(HttpHeaders.AUTHORIZATION, "Bearer " + userAccessToken) 75 | .accept(MediaType.APPLICATION_JSON_VALUE)) 76 | .andDo(print()) 77 | .andExpect(status().is2xxSuccessful()); 78 | } 79 | 80 | @Test 81 | public void user_access_denied_test() throws Exception { 82 | mvc 83 | .perform(get("/api/admin") 84 | .header(HttpHeaders.AUTHORIZATION, "Bearer " + userAccessToken) 85 | .accept(MediaType.APPLICATION_JSON_VALUE)) 86 | .andDo(print()) 87 | .andExpect(status().is(HttpStatus.FORBIDDEN.value())); 88 | } 89 | 90 | @Test 91 | public void admin_test() throws Exception { 92 | mvc 93 | .perform(get("/api/mypage") 94 | .header(HttpHeaders.AUTHORIZATION, "Bearer " + adminAccessToken) 95 | .accept(MediaType.APPLICATION_JSON_VALUE)) 96 | .andDo(print()) 97 | .andExpect(status().is2xxSuccessful()); 98 | } 99 | @Test 100 | public void admin_access_test() throws Exception { 101 | mvc 102 | .perform(get("/api/admin") 103 | .header(HttpHeaders.AUTHORIZATION, "Bearer " + adminAccessToken) 104 | .accept(MediaType.APPLICATION_JSON_VALUE)) 105 | .andDo(print()) 106 | .andExpect(status().is2xxSuccessful()); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/com/okihouse/web/WebFormSecurityTests.java: -------------------------------------------------------------------------------- 1 | package com.okihouse.web; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.test.context.ContextConfiguration; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | import org.springframework.test.context.web.WebAppConfiguration; 12 | import org.springframework.test.web.servlet.MockMvc; 13 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 14 | import org.springframework.web.context.WebApplicationContext; 15 | 16 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; 17 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; 18 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; 19 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 20 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 21 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 22 | 23 | 24 | @RunWith(SpringRunner.class) 25 | @SpringBootTest 26 | @WebAppConfiguration 27 | @ContextConfiguration 28 | public class WebFormSecurityTests { 29 | 30 | @Autowired 31 | private WebApplicationContext context; 32 | 33 | private MockMvc mvc; 34 | 35 | @Before 36 | public void setup() { 37 | mvc = MockMvcBuilders 38 | .webAppContextSetup(context) 39 | .apply(springSecurity()) 40 | .build(); 41 | } 42 | 43 | @Test 44 | public void formLogin_test() throws Exception { 45 | mvc 46 | .perform( 47 | formLogin("/loginProcess") 48 | .user("user") 49 | .password("password")) 50 | .andDo(print()) 51 | .andExpect(status().is3xxRedirection()); // Redirect to mypage when login passed. 52 | } 53 | 54 | @Test 55 | public void ajaxLogin_test() throws Exception { 56 | mvc 57 | .perform( 58 | post("/loginProcess") 59 | .contentType(MediaType.APPLICATION_FORM_URLENCODED) 60 | .accept(MediaType.APPLICATION_JSON_VALUE) 61 | .header("X-Requested-With", "XMLHttpRequest") 62 | .with(csrf()) 63 | .param("username", "user") 64 | .param("password", "password")) 65 | .andDo(print()) 66 | .andExpect(status().is2xxSuccessful()); 67 | } 68 | } 69 | --------------------------------------------------------------------------------