├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── zolvces │ │ └── securityjwt │ │ ├── SecurityJwtApplication.java │ │ ├── controller │ │ └── TestController.java │ │ └── security │ │ ├── JwtKeyConfig.java │ │ └── simple │ │ ├── AccessDecisionService.java │ │ ├── ApplicationConfigurerAdapter.java │ │ ├── JwtAuthenticationProvider.java │ │ ├── JwtHeadFilter.java │ │ ├── JwtLoginFilter.java │ │ ├── JwtLoginToken.java │ │ ├── JwtUser.java │ │ ├── JwtUserDetailServiceImpl.java │ │ ├── LoginFailureHandler.java │ │ └── LoginSuccessHandler.java └── resources │ └── application.yml └── test └── java └── com └── zolvces └── securityjwt └── SecurityJwtApplicationTests.java /README.md: -------------------------------------------------------------------------------- 1 | # security-jwt 2 | ## 说明 3 | 4 | 1. 这是一个比较详尽的SpringSecurity整合JWT的例子 5 | 2. 本文并没有使用spring oauth2,不要搞混 6 | 3. 本文中的原理解释只是大概的介绍,在代码中有非常非常多的注释,配合本文食用更佳 7 | 8 | ## 自定义登录流程 9 | 10 | * **JwtLoginFilter** 自定义的登录过滤器,把它加到SpringSecurity的过滤链中,拦截登录请求它干的事有 11 | 12 | 1. 设置登录的url,请求的方式,其实也就是定义这个过滤器要拦截哪个请求 13 | 14 | 2. 调用JwtAuthenticationProvider进行登录校验 15 | 16 | 3. 校验成功调用LoginSuccessHandler,校验失败调用LoginSuccessHandler 17 | 18 | 19 | 20 | * **JwtAuthenticationProvider** 自定义的认证器,账号密码对不对等校验就是它干的,主要功能 21 | 1. 首先规定自己支持校验那种凭证(Authentication) 22 | 23 | 2. 进行用户校验,调用JwtUserDetailServiceImpl 查询当前用户(JwtUser),判断用户账号密码是否正确,用户是否过期,被锁定等等 24 | 25 | 3. 若用户校验失败则抛异常给JwtLoginFilter,JwtLoginFilter捕获异常调用登录失败的处理类(LoginFailureHandler) 26 | 27 | 4. 若用户校验成功,则生成一个已认证的凭证,也就是Authentication,对应本例的JwtLoginToken 并返回给JwtLoginFilter,JwtLoginFilter拿到凭证后调用登陆成功的处理类LoginSuccessHandler 28 | 29 | 30 | 31 | * **JwtLoginToken** 它就是上面说的凭证,继承自Authentication 32 | 33 | 1. 保存当前用户的认证信息,如认证状态,用户名密码,拥有的权限等 34 | 35 | 36 | 37 | * **JwtUser** 用户实体,实现UserDetails,UserDetails为springSecurity默认的用户实体抽象 38 | 39 | 1. 主要需要实现UserDetails的几个方法,如获取用户名,密码,获取用户冻结状态等 40 | 41 | 42 | 43 | * **JwtUserDetailServiceImpl** UserDetailsService的实现,提供根据用户名查询用户信息的功能 44 | JwtAuthenticationProvider在进行登录信息校验时就会通过它查询用户信息 45 | 46 | 47 | 48 | * **LoginFailureHandler** 登录失败的处理类,被JwtLoginFilter调用,JwtLoginFilter捕获到异常,就会调用它,并且把异常信息传给它 49 | 50 | 51 | 52 | * **LoginSuccessHandler** 登录成功的处理类,被JwtLoginFilter调用,并把JwtAuthenticationProvider创建的凭证(JwtLoginToken)传给它,它就可以根据凭证里的认证信息进行登录成功的处理,如生成token等 53 | 54 | ## 自定义token校验 55 | 在登录过程中,登录成功,调用LoginSuccessHandler生成了token返回给前端,那么登录成功后访问其他路径,如何根据token进行权限校验呢 56 | 57 | * **JwtKeyConfig** 自定义的一个配置类,配置jwt,我这里的签名验证用的是RSA加密,在这里配置了密钥对 58 | 59 | 60 | 61 | * **JwtHeadFilter** 实现token校验的核心,这是自定义的过滤器,主要是请求通过过滤器时,会对其携带的token进行解析和校验 62 | 1. 获取请求中携带的token 63 | 2. 若没有获取到token则return,调交给接下来的过滤器链处理 64 | 3. 若有token,但是校验失败,进行校验失败处理 65 | 4. 若token校验成功,通过从token中获取的用户信息生成一个凭证(Authentication),并放置到SecurityContext 66 | 67 | 在上面的2中没有获取到token为什么这么处理,首先springSecurity判断用户是否认证成功的标志是SecurityContext中是否有凭证(Authentication),在过滤链中,最后部分有一个匿名过滤器(AnonymousAuthenticationFilter),请求经过这个过滤器,若SecurityContext中没有凭证,会被设置一个匿名凭证. 68 | 69 | 最后决定请求是否通过的过滤器是FilterSecurityInterceptor,它会调用WebExpressionVoter来决定当前用户是否是否有权限访问url,若没有权限就会抛出AccessDeniedException,当抛出这个异常时就会有两种处理条件,若SecurityContext 中的凭证是匿名的就表示请求中没有token,需要登录,若凭证不是匿名的就表示当前用户没有权限访问次URL. 70 | 71 | 上面这个判断逻辑发生在ExceptionTranslationFilter过滤器中,抛出异常时对应的操作可以在WebSecurityConfigurerAdapter中的configure方法中配置 72 | 73 | ```java 74 | ...... 75 | http 76 | //身份验证入口,当需要登录却没登录时调用 77 | //具体为,当抛出AccessDeniedException异常时且当前是匿名用户时调用 78 | //匿名用户: 当过滤器链走到匿名过滤器(AnonymousAuthenticationFilter)时, 79 | //会进行判断SecurityContext是否有凭证(Authentication),若前面的过滤器都没有提供凭证, 80 | //匿名过滤器会给SecurityContext提供一个匿名的凭证(可以理解为用户名和权限为anonymous的Authentication), 81 | //这也是JwtHeadFilter发现请求头中没有jwtToken不作处理而直接进入下一个过滤器的原因 82 | .exceptionHandling().authenticationEntryPoint((request, response, authException) -> { 83 | response.setContentType("application/json;charset=UTF-8"); 84 | response.getWriter().write("需要登陆"); 85 | }) 86 | 87 | //拒绝访问处理,当已登录,但权限不足时调用 88 | //抛出AccessDeniedException异常时且当不是匿名用户时调用 89 | .accessDeniedHandler((request, response, accessDeniedException) -> { 90 | response.setContentType("application/json;charset=UTF-8"); 91 | response.getWriter().write("没有权限"); 92 | }) 93 | ...... 94 | ``` 95 | 96 | ## 运行demo 97 | 什么也不需要配置,直接运行就行,提供了一个testController,运行期望如下 98 | ```java 99 | /**任何人都能访问 100 | * @return 101 | */ 102 | @GetMapping("/publicMsg") 103 | public String getMsg(){ 104 | return "you get the message!"; 105 | } 106 | 107 | /**登录的用户才能访问 108 | * @return 109 | */ 110 | @GetMapping("/innerMsg") 111 | public String innerMsg(){ 112 | return "you get the message!"; 113 | } 114 | 115 | /**管理员(admin)才能访问 116 | * @return 117 | */ 118 | @GetMapping("/secret") 119 | public String secret(){ 120 | return "you get the message!"; 121 | } 122 | ``` 123 | 如果这对你有所帮助,麻烦给个star 124 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.3.RELEASE 9 | 10 | 11 | com.zolvces 12 | security-jwt 13 | 0.0.1-SNAPSHOT 14 | security-jwt 15 | Demo project for Spring Boot 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-security 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-web 29 | 30 | 31 | 32 | org.springframework.security 33 | spring-security-jwt 34 | 1.0.10.RELEASE 35 | 36 | 37 | 38 | com.alibaba 39 | fastjson 40 | 1.2.56 41 | 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-test 47 | test 48 | 49 | 50 | org.springframework.security 51 | spring-security-test 52 | test 53 | 54 | 55 | 56 | 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-maven-plugin 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/main/java/com/zolvces/securityjwt/SecurityJwtApplication.java: -------------------------------------------------------------------------------- 1 | package com.zolvces.securityjwt; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SecurityJwtApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SecurityJwtApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/zolvces/securityjwt/controller/TestController.java: -------------------------------------------------------------------------------- 1 | package com.zolvces.securityjwt.controller; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | /** 7 | * @author niXueChao 8 | * @date 2019/4/2 23:34. 9 | */ 10 | @RestController 11 | public class TestController { 12 | 13 | 14 | /**任何人都能访问 15 | * @return 16 | */ 17 | @GetMapping("/publicMsg") 18 | public String getMsg(){ 19 | return "you get the message!"; 20 | } 21 | 22 | /**登录的用户才能访问 23 | * @return 24 | */ 25 | @GetMapping("/innerMsg") 26 | public String innerMsg(){ 27 | return "you get the message!"; 28 | } 29 | 30 | /**管理员(admin)才能访问 31 | * @return 32 | */ 33 | @GetMapping("/secret") 34 | public String secret(){ 35 | return "you get the message!"; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/zolvces/securityjwt/security/JwtKeyConfig.java: -------------------------------------------------------------------------------- 1 | package com.zolvces.securityjwt.security; 2 | 3 | 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.security.jwt.crypto.sign.RsaSigner; 7 | import org.springframework.security.jwt.crypto.sign.RsaVerifier; 8 | 9 | /** 10 | * @author niXueChao 11 | * @date 2019/3/29 14:30. 12 | */ 13 | @Configuration 14 | public class JwtKeyConfig { 15 | 16 | /**RSA私钥*/ 17 | private static final String PRIVATE_KEY ="-----BEGIN RSA PRIVATE KEY-----" + 18 | "MIICWwIBAAKBgQCM1YBbzMijYIp4/mf1+gdVBXQMJEv5KpuTDh6DiTGJAk1yrsWA" + 19 | "RfqjpC83/t0xzpmvHa1M7WykUg5E0PmneNddyD/MTjkCDNhqBgr0AnJTZsTnEjMa" + 20 | "PB0cXeVF1ty1p+ZBuvHKMvhJwqgNmQd7uGpl2Rq1gR1L86YTWSkYceSoNwIDAQAB" + 21 | "AoGAcYrr+pcGp5l86oGJhWm4IZbM8cENs2vjk9LNTRT9580AbdZ0Cq/gm7ASFZ4X" + 22 | "7UD47JMLljrQ3UX+lQK6VIf7cTUGZdR1nVArOqJaMKVvCYkwqR6bm5Gc6qx6XWAW" + 23 | "0/PY2LcWt0cW1Q1CU65M1oM08P+ohQvE4kJI45RcoIl6VwkCQQCO0Za4bYiZWtzE" + 24 | "UzRka+kHa//h1YjYbQVglPLb5FuOdSm62eGQThfQRpyLU1WD6sATv9yPWxUaRCEL" + 25 | "Fh+s/YfrAkEA/HFDLl/Nl439/A5Q05HWhMKWZ8tt8k448mNNlefJUK1ApCuWdDWm" + 26 | "kBTk8ytjRvdFlVKvVVXUV8LeSyWMXpR55QJACe/rXMnCR2lbEw33B0W64RlSpJQH" + 27 | "AYgUZ7P1cfdhp3fff3DJkRDd90/ydH9H4/Xhh35CCnd78GftJKhVa+P4IQJABYv3" + 28 | "je1M9yeHjSJDZGKv8/rSkzVFFS3i0nCcI88T/VHROco7ZBJJtqC+5xjs9YI5ZS6L" + 29 | "67QXFlaRy9TnYKyigQJAHjuzdwDgKBj2orf6k05ri+Ks1nGvp5S4JxzcGCmkQB+l" + 30 | "6KOJ8lAFma4qxWKaMeNi0ekrzkSrJNEt5yJPbw1Lmg==" + 31 | "-----END RSA PRIVATE KEY-----"; 32 | 33 | /**使用私钥签名*/ 34 | @Bean 35 | public RsaSigner getSigner(){ 36 | return new RsaSigner(PRIVATE_KEY); 37 | } 38 | 39 | /**使用公钥验签(这里是可以通过私钥生成密钥对,包含公钥)*/ 40 | @Bean 41 | public RsaVerifier getVerifier(){ 42 | return new RsaVerifier(PRIVATE_KEY); 43 | } 44 | 45 | // public static void main(String[] args) { 46 | // String jsonString = JSON.toJSONString(new HashMap() {{ 47 | // put("name", "zhangsan"); 48 | // put("id", "343"); 49 | // }}); 50 | // System.out.println(JwtHelper.encode(jsonString, getSigner()).getEncoded()); 51 | // Jwt decode = JwtHelper.decode("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.aGhoaGhoaGhoaGhoaGhoaA.NXL4cJ9zMkKmaT2JnuYmr_sMRm51mil5ueje73NP5s96pOWPdHgUU875iFL-DabNu3hYOGEjO47rWnxTjzug9S_XOry7aAcKFA-cN3ROAD8rXON-dIH0gNnBYYcIWzcTAfvtGCNQjUrXyL4nxypBqog5Plw8k7V-6hS1L4PZYnM"); 52 | // System.out.println(decode.getClaims()); 53 | // } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/zolvces/securityjwt/security/simple/AccessDecisionService.java: -------------------------------------------------------------------------------- 1 | package com.zolvces.securityjwt.security.simple; 2 | 3 | import org.springframework.security.authentication.AnonymousAuthenticationToken; 4 | import org.springframework.security.core.Authentication; 5 | import org.springframework.security.core.userdetails.UserDetails; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.util.AntPathMatcher; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | /** 配置路径访问限制,若你的用户角色比较简单,不需要存数据库, 15 | * 可以在ApplicationConfigurerAdapter里配置如 16 | * httpSecurity 17 | * .authorizeRequests() 18 | * .antMatchers("/order")..... 19 | * 20 | * @author niXueChao 21 | * @date 2019/4/10 10:33. 22 | */ 23 | @Component("accessDecisionService") 24 | public class AccessDecisionService { 25 | 26 | private AntPathMatcher antPathMatcher = new AntPathMatcher(); 27 | 28 | public boolean hasPermission(HttpServletRequest request, Authentication auth) { 29 | 30 | //不需要登录也能访问的(permitAll) 31 | for (String url : Arrays.asList("/publicMsg")) { 32 | if (antPathMatcher.match(url, request.getRequestURI())) { 33 | return true; 34 | } 35 | } 36 | 37 | if (auth instanceof AnonymousAuthenticationToken) { 38 | return false; 39 | } 40 | 41 | UserDetails user = (UserDetails) auth.getPrincipal(); 42 | String userName = user.getUsername(); 43 | //根据用户名查出能访问哪些url, urls=findUrlByUserName() 44 | List urls = queryUrlByUserName(userName); 45 | for (String url : urls) { 46 | if (antPathMatcher.match(url, request.getRequestURI())) { 47 | return true; 48 | } 49 | } 50 | return false; 51 | } 52 | 53 | /** 54 | * 模拟数据库查询用户权限 55 | * 56 | * @param userName 57 | * @return 58 | */ 59 | private List queryUrlByUserName(String userName) { 60 | switch (userName) { 61 | case "admin": 62 | return Arrays.asList("/innerMsg", "/secret"); 63 | case "user": 64 | return Arrays.asList("/innerMsg"); 65 | default: 66 | return new ArrayList<>(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/zolvces/securityjwt/security/simple/ApplicationConfigurerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.zolvces.securityjwt.security.simple; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.HttpMethod; 7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 8 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 9 | import org.springframework.security.config.http.SessionCreationPolicy; 10 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 11 | import org.springframework.security.crypto.password.PasswordEncoder; 12 | import org.springframework.security.jwt.crypto.sign.RsaSigner; 13 | import org.springframework.security.jwt.crypto.sign.RsaVerifier; 14 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 15 | 16 | /** 17 | * @author niXueChao 18 | * @date 2019/4/2 22:58. 19 | */ 20 | @Configuration 21 | public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter { 22 | 23 | @Autowired 24 | private RsaVerifier verifier; 25 | 26 | @Autowired 27 | private RsaSigner signer; 28 | 29 | @Autowired 30 | private JwtUserDetailServiceImpl jwtUserDetailService; 31 | 32 | 33 | @Override 34 | protected void configure(HttpSecurity http) throws Exception { 35 | //登录过滤器 36 | JwtLoginFilter jwtLoginFilter = new JwtLoginFilter(); 37 | jwtLoginFilter.setAuthenticationManager(this.authenticationManagerBean()); 38 | 39 | //登录成功和失败的操作 40 | LoginSuccessHandler loginSuccessHandler = new LoginSuccessHandler(); 41 | loginSuccessHandler.setSigner(signer); 42 | jwtLoginFilter.setAuthenticationSuccessHandler(loginSuccessHandler); 43 | jwtLoginFilter.setAuthenticationFailureHandler(new LoginFailureHandler()); 44 | 45 | //登录过滤器的授权提供者(就这么叫吧) 46 | JwtAuthenticationProvider provider = new JwtAuthenticationProvider(); 47 | provider.setPasswordEncoder(passwordEncoder()); 48 | provider.setUserDetailsService(jwtUserDetailService); 49 | 50 | //JWT校验过滤器 51 | JwtHeadFilter headFilter = new JwtHeadFilter(); 52 | headFilter.setVerifier(verifier); 53 | 54 | http 55 | //身份验证入口,当需要登录却没登录时调用 56 | //具体为,当抛出AccessDeniedException异常时且当前是匿名用户时调用 57 | //匿名用户: 当过滤器链走到匿名过滤器(AnonymousAuthenticationFilter)时, 58 | //会进行判断SecurityContext是否有凭证(Authentication),若前面的过滤器都没有提供凭证, 59 | //匿名过滤器会给SecurityContext提供一个匿名的凭证(可以理解为用户名和权限为anonymous的Authentication), 60 | //这也是JwtHeadFilter发现请求头中没有jwtToken不作处理而直接进入下一个过滤器的原因 61 | .exceptionHandling().authenticationEntryPoint((request, response, authException) -> { 62 | response.setContentType("application/json;charset=UTF-8"); 63 | response.getWriter().write("需要登陆"); 64 | }) 65 | 66 | //拒绝访问处理,当已登录,但权限不足时调用 67 | //抛出AccessDeniedException异常时且当不是匿名用户时调用 68 | .accessDeniedHandler((request, response, accessDeniedException) -> { 69 | response.setContentType("application/json;charset=UTF-8"); 70 | response.getWriter().write("没有权限"); 71 | }) 72 | .and() 73 | .authorizeRequests() 74 | 75 | .anyRequest().access("@accessDecisionService.hasPermission(request , authentication)") 76 | .and() 77 | //将授权提供者注册到授权管理器中(AuthenticationManager) 78 | .authenticationProvider(provider) 79 | .addFilterAfter(jwtLoginFilter, UsernamePasswordAuthenticationFilter.class) 80 | .addFilterAfter(headFilter, JwtLoginFilter.class) 81 | //禁用session 82 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 83 | .and() 84 | .csrf().disable(); 85 | } 86 | 87 | 88 | @Bean 89 | public PasswordEncoder passwordEncoder() { 90 | return new BCryptPasswordEncoder(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/zolvces/securityjwt/security/simple/JwtAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package com.zolvces.securityjwt.security.simple; 2 | 3 | import org.springframework.security.authentication.*; 4 | import org.springframework.security.core.Authentication; 5 | import org.springframework.security.core.AuthenticationException; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | import org.springframework.security.core.userdetails.UserDetailsService; 8 | import org.springframework.security.crypto.password.PasswordEncoder; 9 | 10 | /**认证器 11 | * @author niXueChao 12 | * @date 2019/4/2 15:59. 13 | */ 14 | 15 | public class JwtAuthenticationProvider implements AuthenticationProvider { 16 | 17 | /**供根据用户名查询用户,获取UserDetails的方法*/ 18 | private UserDetailsService userDetailsService; 19 | 20 | /**提供加密方式,密码验证时,需要加密后进行对比*/ 21 | private PasswordEncoder passwordEncoder; 22 | 23 | 24 | /** 认证提供者进行认证,注意这里传入的authentication对象,是JwtLoginFilter里调用 25 | * @see JwtLoginToken#JwtLoginToken(Object, Object) 方法生成的,是未认证状态的(setAuthenticated(false)) 26 | * 此方法会返回一个已认证状态的authentication 27 | * @param authentication 28 | * @return 29 | * @throws AuthenticationException 30 | */ 31 | @Override 32 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 33 | String userName= authentication.getName(); 34 | //获取用户 35 | UserDetails userDetails = userDetailsService.loadUserByUsername(userName); 36 | //转换authentication 认证时会先调用support方法,受支持才会调用,所以能强转 37 | JwtLoginToken jwtLoginToken= (JwtLoginToken) authentication; 38 | //检查 39 | defaultCheck(userDetails); 40 | additionalAuthenticationChecks(userDetails,jwtLoginToken); 41 | //构造已认证的authentication 42 | JwtLoginToken authenticatedToken = new JwtLoginToken(userDetails, jwtLoginToken.getCredentials(), userDetails.getAuthorities()); 43 | authenticatedToken.setDetails(jwtLoginToken.getDetails()); 44 | return authenticatedToken; 45 | } 46 | 47 | /**这个provider支持哪种凭证(token)的认证*/ 48 | @Override 49 | public boolean supports(Class authentication) { 50 | return (JwtLoginToken.class 51 | .isAssignableFrom(authentication)); 52 | } 53 | 54 | /**(附加检查项)检查密码是否正确*/ 55 | private void additionalAuthenticationChecks(UserDetails userDetails, 56 | JwtLoginToken authentication) throws AuthenticationException { 57 | if (authentication.getCredentials() == null) { 58 | throw new BadCredentialsException("Bad credentials"); 59 | } 60 | String presentedPassword = authentication.getCredentials().toString(); 61 | if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { 62 | throw new BadCredentialsException("Bad credentials"); 63 | } 64 | } 65 | 66 | /**用户默认检查,用户是否锁定过期等*/ 67 | private void defaultCheck(UserDetails user) { 68 | if (!user.isAccountNonLocked()) { 69 | throw new LockedException("User account is locked"); 70 | } 71 | 72 | if (!user.isEnabled()) { 73 | throw new DisabledException("User is disabled"); 74 | } 75 | 76 | if (!user.isAccountNonExpired()) { 77 | throw new AccountExpiredException("User account has expired"); 78 | } 79 | } 80 | 81 | public void setUserDetailsService(UserDetailsService userDetailsService) { 82 | this.userDetailsService = userDetailsService; 83 | } 84 | 85 | public void setPasswordEncoder(PasswordEncoder passwordEncoder) { 86 | this.passwordEncoder = passwordEncoder; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/zolvces/securityjwt/security/simple/JwtHeadFilter.java: -------------------------------------------------------------------------------- 1 | package com.zolvces.securityjwt.security.simple; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import org.springframework.security.core.context.SecurityContextHolder; 5 | import org.springframework.security.jwt.Jwt; 6 | import org.springframework.security.jwt.JwtHelper; 7 | import org.springframework.security.jwt.crypto.sign.RsaVerifier; 8 | import org.springframework.security.web.authentication.WebAuthenticationDetails; 9 | import org.springframework.web.filter.OncePerRequestFilter; 10 | 11 | import javax.servlet.FilterChain; 12 | import javax.servlet.ServletException; 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletResponse; 15 | import java.io.IOException; 16 | 17 | /**拦截请求进行token验证 18 | * @author niXueChao 19 | * @date 2019/4/3 15:03. 20 | */ 21 | public class JwtHeadFilter extends OncePerRequestFilter { 22 | private RsaVerifier verifier; 23 | 24 | @Override 25 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 26 | String token = request.getHeader("Authentication"); 27 | if (token==null || token.isEmpty()){ 28 | filterChain.doFilter(request,response); 29 | return; 30 | } 31 | 32 | JwtUser user; 33 | try { 34 | Jwt jwt = JwtHelper.decodeAndVerify(token, verifier); 35 | String claims = jwt.getClaims(); 36 | user = JSON.parseObject(claims, JwtUser.class); 37 | //todo: 可以在这里添加检查用户是否过期,冻结... 38 | }catch (Exception e){ 39 | //这里也可以filterChain.doFilter(request,response)然后return,那最后就会调用 40 | //.exceptionHandling().authenticationEntryPoint,也就是本列中的"需要登陆" 41 | response.setContentType("application/json;charset=UTF-8"); 42 | response.getWriter().write("token 失效"); 43 | return; 44 | } 45 | JwtLoginToken jwtLoginToken = new JwtLoginToken(user, "", user.getAuthorities()); 46 | jwtLoginToken.setDetails(new WebAuthenticationDetails(request)); 47 | SecurityContextHolder.getContext().setAuthentication(jwtLoginToken); 48 | filterChain.doFilter(request,response); 49 | } 50 | 51 | 52 | public void setVerifier(RsaVerifier verifier) { 53 | this.verifier = verifier; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/zolvces/securityjwt/security/simple/JwtLoginFilter.java: -------------------------------------------------------------------------------- 1 | package com.zolvces.securityjwt.security.simple; 2 | 3 | import org.springframework.security.authentication.BadCredentialsException; 4 | import org.springframework.security.core.Authentication; 5 | import org.springframework.security.core.AuthenticationException; 6 | import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; 7 | import org.springframework.security.web.authentication.WebAuthenticationDetails; 8 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 9 | 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | 13 | /** 14 | * @author niXueChao 15 | * @date 2019/4/2 14:07. 16 | */ 17 | public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter { 18 | 19 | 20 | JwtLoginFilter() { 21 | super(new AntPathRequestMatcher("/jwtLogin", "GET")); 22 | } 23 | 24 | 25 | @Override 26 | public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { 27 | try { 28 | String userName = request.getParameter("userName"); 29 | String password = request.getParameter("password"); 30 | //创建未认证的凭证(etAuthenticated(false)),注意此时凭证中的主体principal为用户名 31 | JwtLoginToken jwtLoginToken = new JwtLoginToken(userName, password); 32 | //将认证详情(ip,sessionId)写到凭证 33 | jwtLoginToken.setDetails(new WebAuthenticationDetails(request)); 34 | //AuthenticationManager获取受支持的AuthenticationProvider(这里也就是JwtAuthenticationProvider), 35 | //生成已认证的凭证,此时凭证中的主体为userDetails 36 | Authentication authenticatedToken = this.getAuthenticationManager().authenticate(jwtLoginToken); 37 | return authenticatedToken; 38 | }catch (Exception e){ 39 | throw new BadCredentialsException("坏的凭证"); 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/zolvces/securityjwt/security/simple/JwtLoginToken.java: -------------------------------------------------------------------------------- 1 | package com.zolvces.securityjwt.security.simple; 2 | 3 | import org.springframework.security.authentication.AbstractAuthenticationToken; 4 | import org.springframework.security.core.GrantedAuthority; 5 | 6 | import java.util.Collection; 7 | 8 | /** 9 | * @author niXueChao 10 | * @date 2019/4/2 14:35. 11 | */ 12 | public class JwtLoginToken extends AbstractAuthenticationToken { 13 | 14 | /**登录用户信息*/ 15 | private final Object principal; 16 | /**密码*/ 17 | private Object credentials; 18 | 19 | /**创建一个未认证的授权令牌, 20 | * 这时传入的principal是用户名 21 | * 22 | */ 23 | public JwtLoginToken(Object principal, Object credentials) { 24 | super(null); 25 | this.principal = principal; 26 | this.credentials = credentials; 27 | setAuthenticated(false); 28 | } 29 | 30 | /**创建一个已认证的授权令牌,如注释中说的那样,这个方法应该由AuthenticationProvider来调用 31 | * 也就是我们写的JwtAuthenticationProvider,有它完成认证后再调用这个方法, 32 | * 这时传入的principal为从userService中查出的UserDetails 33 | * @param principal 34 | * @param credentials 35 | * @param authorities 36 | */ 37 | public JwtLoginToken(Object principal, Object credentials, 38 | Collection authorities) { 39 | super(authorities); 40 | this.principal = principal; 41 | this.credentials = credentials; 42 | super.setAuthenticated(true); 43 | } 44 | 45 | @Override 46 | public Object getCredentials() { 47 | return this.credentials; 48 | } 49 | 50 | @Override 51 | public Object getPrincipal() { 52 | return this.principal; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/zolvces/securityjwt/security/simple/JwtUser.java: -------------------------------------------------------------------------------- 1 | package com.zolvces.securityjwt.security.simple; 2 | 3 | import org.springframework.security.core.GrantedAuthority; 4 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 5 | import org.springframework.security.core.userdetails.UserDetails; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | /** 13 | * @author niXueChao 14 | * @date 2019/4/8 11:29. 15 | */ 16 | public class JwtUser implements UserDetails { 17 | private String username; 18 | private String password; 19 | private List authorities; 20 | 21 | public JwtUser() { 22 | } 23 | 24 | public JwtUser(String username, String password, String ... roles) { 25 | this.username = username; 26 | this.password = password; 27 | this.authorities= Arrays.stream(roles).map(SimpleGrantedAuthority::new).collect(Collectors.toList()); 28 | } 29 | 30 | @Override 31 | public Collection getAuthorities() { 32 | return this.authorities; 33 | } 34 | 35 | @Override 36 | public String getPassword() { 37 | return this.password; 38 | } 39 | 40 | @Override 41 | public String getUsername() { 42 | return this.username; 43 | } 44 | 45 | @Override 46 | public boolean isAccountNonExpired() { 47 | return true; 48 | } 49 | 50 | @Override 51 | public boolean isAccountNonLocked() { 52 | return true; 53 | } 54 | 55 | @Override 56 | public boolean isCredentialsNonExpired() { 57 | return true; 58 | } 59 | 60 | @Override 61 | public boolean isEnabled() { 62 | return true; 63 | } 64 | 65 | public void setUsername(String username) { 66 | this.username = username; 67 | } 68 | 69 | public void setPassword(String password) { 70 | this.password = password; 71 | } 72 | 73 | public void setAuthorities(List authorities) { 74 | this.authorities = authorities; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/zolvces/securityjwt/security/simple/JwtUserDetailServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.zolvces.securityjwt.security.simple; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.security.core.userdetails.UserDetails; 5 | import org.springframework.security.core.userdetails.UserDetailsService; 6 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 7 | import org.springframework.security.crypto.password.PasswordEncoder; 8 | import org.springframework.stereotype.Component; 9 | 10 | /** 11 | * @author niXueChao 12 | * @date 2019/4/8 11:26. 13 | */ 14 | @Component 15 | public class JwtUserDetailServiceImpl implements UserDetailsService { 16 | 17 | private final PasswordEncoder passwordEncoder; 18 | 19 | @Autowired 20 | public JwtUserDetailServiceImpl(PasswordEncoder passwordEncoder) { 21 | this.passwordEncoder = passwordEncoder; 22 | } 23 | 24 | /** 模拟数据库查询 25 | * @param username 26 | * @return 27 | * @throws UsernameNotFoundException 28 | */ 29 | @Override 30 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 31 | if ("admin".equals(username)) { 32 | return new JwtUser("admin", passwordEncoder.encode("123456")); 33 | } 34 | if ("user".equals(username)) { 35 | return new JwtUser("user", passwordEncoder.encode("123456")); 36 | } 37 | return null; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/zolvces/securityjwt/security/simple/LoginFailureHandler.java: -------------------------------------------------------------------------------- 1 | package com.zolvces.securityjwt.security.simple; 2 | 3 | import org.springframework.security.core.AuthenticationException; 4 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; 5 | 6 | import javax.servlet.ServletException; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | import java.io.IOException; 10 | 11 | /** 12 | * @author niXueChao 13 | * @date 2019/4/3 23:05. 14 | */ 15 | public class LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler { 16 | 17 | @Override 18 | public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { 19 | response.setContentType("application/json;charset=UTF-8"); 20 | response.getWriter().write("登录失败"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/zolvces/securityjwt/security/simple/LoginSuccessHandler.java: -------------------------------------------------------------------------------- 1 | package com.zolvces.securityjwt.security.simple; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import org.springframework.security.core.Authentication; 5 | import org.springframework.security.jwt.JwtHelper; 6 | import org.springframework.security.jwt.crypto.sign.RsaSigner; 7 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.io.IOException; 12 | 13 | /**登录成功 14 | * @author niXueChao 15 | * @date 2019/3/12. 16 | */ 17 | public class LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { 18 | 19 | private RsaSigner signer; 20 | 21 | @Override 22 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { 23 | response.setContentType("application/json;charset=UTF-8"); 24 | String userJsonStr = JSON.toJSONString(authentication.getPrincipal()); 25 | String token = JwtHelper.encode(userJsonStr, signer).getEncoded(); 26 | //签发token 27 | response.getWriter().write("token="+token); 28 | } 29 | 30 | public void setSigner(RsaSigner signer) { 31 | this.signer = signer; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8090 3 | logging: 4 | level: 5 | root: debug -------------------------------------------------------------------------------- /src/test/java/com/zolvces/securityjwt/SecurityJwtApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.zolvces.securityjwt; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SecurityJwtApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------