├── 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 extends GrantedAuthority> 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 extends GrantedAuthority> 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 |
--------------------------------------------------------------------------------