kongzy
2023-09-22 3124f3a5b7f45d043b228829b6b3a2e541b31574
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package com.nms.swspkmas_standalone.filter;
 
import cn.hutool.crypto.digest.MD5;
 
import com.alibaba.fastjson.JSONObject;
import com.nms.swspkmas_standalone.response.CommonResult;
import com.nms.swspkmas_standalone.response.ResultCode;
import com.nms.swspkmas_standalone.shiro.token.JwtToken;
import com.nms.swspkmas_standalone.utils.JwtTokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
 
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
 
/**
 * @Author ling.quan
 * @Date 2022/2/17 15:27
 * @Desciption
 */
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {
 
    // 存放token的map key为登录时的token加密,value为验证token
    public final static Map<String, String> tokenMap = new HashMap<>();
 
    /**
     * 前置拦截处理
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        //servlet请求与响应的转换
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
 
        //跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if(httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())){
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }
 
    /**
     * 后置拦截处理
     * @param request
     * @param response
     * @throws Exception
     */
    @Override
    protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
        //添加跨域支持
        this.fillCorsHeader(WebUtils.toHttp(request), WebUtils.toHttp(response));
    }
 
    /**
     * 过滤器拦截请求的入口方法,所有请求都会进入该方法
     * 返回true则允许访问
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        // 原用来判断是否是登录请求,在本例中不会拦截登录请求,用来检测Header是否包含token字段
        if(this.isLoginRequest(request,response)){//看看源码,调用了isLoginAttempt()方法
            return false; //返回false后进入onAccessDenied()方法,返回错误信息
        }
 
        boolean allowed = false;
        try{
            //检测header里的JWT Token内容是否正确,尝试使用token进行登录
            allowed = this.executeLogin(request,response);
        } catch (Exception e){ //未找到token
            log.error(e.getMessage());
        }
        return allowed || super.isPermissive(mappedValue);
    }
 
    /**
     * 检测Header中是否包含token字段
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        return ((HttpServletRequest) request).getHeader(JwtTokenUtil.USER_LOGIN_TOKEN) == null;
    }
 
    /**
     * 身份验证,检查JWT token是否合法
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        //从请求头里拿到token
        AuthenticationToken token = createToken(request,response);
        if(token == null){
            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken must be created in order to execute a login attempt.";
            throw new IllegalStateException(msg);
        }
        try{
            Subject subject = getSubject(request,response);
            subject.login(token); //让shiro进行登录验证
            //没出错则验证成功
            return onLoginSuccess(token,subject,request,response);
        } catch (AuthenticationException e){
            return onLoginFailure(token, e, request, response);
        }
    }
 
    /**
     * 从Header中提取 JWT token
     * @param request
     * @param response
     * @return
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String authorization = httpServletRequest.getHeader(JwtTokenUtil.USER_LOGIN_TOKEN);
        return new JwtToken(authorization);
    }
 
    /**
     * isAccessAllowed()方法返回false,会进入该方法,表示拒绝访问
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(HttpStatus.OK.value());
 
        PrintWriter writer = httpServletResponse.getWriter();
 
        writer.write(JSONObject.toJSONString(CommonResult.failed(ResultCode.UNAUTHORIZED)));
        fillCorsHeader(WebUtils.toHttp(request),httpServletResponse);
        return false;
    }
 
    /**
     * shiro利用 JWT Token 登录成功后,进入该方法
     * @param token
     * @param subject
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse httpResponse = WebUtils.toHttp(response);
        String newToken = null;
 
        //登录成功后判断是否需要续签token
        if(token instanceof JwtToken) {
            JwtToken jwtToken = (JwtToken) token;
//            boolean needUpdate = JWTUtils.isNeedUpdate(JwtFilter.tokenMap.get(MD5.create().digestHex(jwtToken.getCredentials().toString())), JwtTokenUtil.expiration);
//            if (needUpdate) {
//                newToken = JWTUtils.createToken(jwtToken.getPrincipal().toString(), JWTUtils.expireTime);
//                JwtFilter.tokenMap.put(MD5.create().digestHex(jwtToken.getCredentials().toString()), newToken);
//            }
        }
//        if(newToken != null){
//            httpResponse.setHeader(JWTUtils.USER_LOGIN_TOKEN,newToken);
//        }
        return true;
    }
 
    /**
     * 利用 JWT token 登录失败,会进入该方法
     * @param token
     * @param e
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        return false;//直接返回false,交给后面的 onAccessDenied()方法处理
    }
    //跨域请求的解决方案之一
    protected void fillCorsHeader(HttpServletRequest request, HttpServletResponse response){
        response.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,HEAD");
        response.setHeader("Access-Control-Allow-Headers",
                request.getHeader("Access-Control-Request-Headers"));
    }
 
}