diff --git a/src/main/java/com/codesdream/ase/component/auth/AJAXRequestChecker.java b/src/main/java/com/codesdream/ase/component/auth/AJAXRequestChecker.java new file mode 100644 index 0000000..77c9bed --- /dev/null +++ b/src/main/java/com/codesdream/ase/component/auth/AJAXRequestChecker.java @@ -0,0 +1,14 @@ +package com.codesdream.ase.component.auth; + +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.Optional; + +@Component +public class AJAXRequestChecker { + public boolean checkAjaxPOSTRequest(HttpServletRequest request){ + return Optional.ofNullable(request.getHeader("X-Requested-With")).isPresent(); + } +} diff --git a/src/main/java/com/codesdream/ase/component/auth/AuthTokenGenerator.java b/src/main/java/com/codesdream/ase/component/auth/AuthTokenGenerator.java new file mode 100644 index 0000000..8977670 --- /dev/null +++ b/src/main/java/com/codesdream/ase/component/auth/AuthTokenGenerator.java @@ -0,0 +1,19 @@ +package com.codesdream.ase.component.auth; + +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.UUID; + +@Component +public class AuthTokenGenerator { + @Resource + private SHA1Encoder encoder; + + public String generateAuthToken(String username){ + Date dateNow = new Date(); + UUID uuid = UUID.randomUUID(); + return encoder.encode(String.format("Token [%s][%d][%s]",username,dateNow.getTime(), uuid.toString())); + } +} diff --git a/src/main/java/com/codesdream/ase/component/auth/JSONRandomCodeGenerator.java b/src/main/java/com/codesdream/ase/component/auth/JSONRandomCodeGenerator.java new file mode 100644 index 0000000..954850b --- /dev/null +++ b/src/main/java/com/codesdream/ase/component/auth/JSONRandomCodeGenerator.java @@ -0,0 +1,19 @@ +package com.codesdream.ase.component.auth; + +import com.alibaba.fastjson.JSONObject; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Date; + +// 随机特征码生成器 +@Component +public class JSONRandomCodeGenerator { + @Resource + private SHA1Encoder encoder; + + public String generateRandomCode(String username, Date date, String clientCode){ + return encoder.encode(String.format("RandomCode [%s][%s][%s]", + username, date.toString(), clientCode)); + } +} diff --git a/src/main/java/com/codesdream/ase/component/auth/JSONSignedGenerator.java b/src/main/java/com/codesdream/ase/component/auth/JSONSignedGenerator.java new file mode 100644 index 0000000..b67a76c --- /dev/null +++ b/src/main/java/com/codesdream/ase/component/auth/JSONSignedGenerator.java @@ -0,0 +1,16 @@ +package com.codesdream.ase.component.auth; + +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +// 用来给JSON生成签名 +@Component +public class JSONSignedGenerator { + @Resource + SHA1Encoder encoder; + + public String generateSigned(String username, String randomCode, String token){ + return encoder.encode(String.format("SIGN [%s][%s][%s]",username, randomCode, token)); + } +} diff --git a/src/main/java/com/codesdream/ase/component/auth/JSONTokenAuthenticationToken.java b/src/main/java/com/codesdream/ase/component/auth/JSONTokenAuthenticationToken.java new file mode 100644 index 0000000..92bf5f9 --- /dev/null +++ b/src/main/java/com/codesdream/ase/component/auth/JSONTokenAuthenticationToken.java @@ -0,0 +1,62 @@ +package com.codesdream.ase.component.auth; + + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; + +// 关联Token与其他用户的相关数据的授权柄 +public class JSONTokenAuthenticationToken extends AbstractAuthenticationToken { + // 客户端签名 + private String signed = null; + // 用户名 + private Object principal = null; + // 客户端代码 + private String clientCode = null; + + + /** + * Creates a token with the supplied array of authorities. + * + * @param authorities the collection of GrantedAuthoritys for the principal + * represented by this authentication object. + */ + public JSONTokenAuthenticationToken(UserDetails principal, + String clientCode, + Collection authorities) + { + super(authorities); + this.principal = principal; + this.clientCode = clientCode; + this.signed = null; + setAuthenticated(true); + } + + public JSONTokenAuthenticationToken(String principal, String clientCode, String signed) { + super(null); + this.principal = principal; + this.clientCode = clientCode; + this.signed = signed; + setAuthenticated(false); + } + + @Override + public String getCredentials() { + return signed; + } + + @Override + public Object getPrincipal() { + return principal; + } + + public String getClientCode() { + return clientCode; + } + + public void setClientCode(String clientCode) { + this.clientCode = clientCode; + } +} diff --git a/src/main/java/com/codesdream/ase/component/auth/JSONTokenUsernamePasswordAuthenticationToken.java b/src/main/java/com/codesdream/ase/component/auth/JSONTokenUsernamePasswordAuthenticationToken.java new file mode 100644 index 0000000..14cc5fd --- /dev/null +++ b/src/main/java/com/codesdream/ase/component/auth/JSONTokenUsernamePasswordAuthenticationToken.java @@ -0,0 +1,38 @@ +package com.codesdream.ase.component.auth; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +// 明文用户名密码验证授权柄 +public class JSONTokenUsernamePasswordAuthenticationToken extends AbstractAuthenticationToken { + // 用户名 + private String username = null; + // 明文密码 + private String password = null; + // 授权柄 + private String clientCode = null; + + public JSONTokenUsernamePasswordAuthenticationToken(String username, String password, String clientCode) { + super(null); + this.username = username; + this.password = password; + this.clientCode = clientCode; + setAuthenticated(false); + } + + @Override + public Object getCredentials() { + return password; + } + + @Override + public Object getPrincipal() { + return username; + } + // 扩展接口 获得客户端代码 + public String getClientCode() { + return clientCode; + } +} diff --git a/src/main/java/com/codesdream/ase/component/auth/SHA1Encoder.java b/src/main/java/com/codesdream/ase/component/auth/SHA1Encoder.java new file mode 100644 index 0000000..e3102d2 --- /dev/null +++ b/src/main/java/com/codesdream/ase/component/auth/SHA1Encoder.java @@ -0,0 +1,17 @@ +package com.codesdream.ase.component.auth; + +import org.apache.commons.codec.cli.Digest; +import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.stereotype.Component; + +// SHA1算法不可逆加密 主要用于JSON签名 +@Component +public class SHA1Encoder { + String encode(CharSequence charSequence){ + return DigestUtils.sha1Hex(charSequence.toString()); + } + + boolean match(CharSequence charSequence, String s){ + return s.equals(encode(charSequence)); + } +} diff --git a/src/main/java/com/codesdream/ase/component/datamanager/JSONParameter.java b/src/main/java/com/codesdream/ase/component/datamanager/JSONParameter.java index 97b932c..103c5a0 100644 --- a/src/main/java/com/codesdream/ase/component/datamanager/JSONParameter.java +++ b/src/main/java/com/codesdream/ase/component/datamanager/JSONParameter.java @@ -2,9 +2,11 @@ package com.codesdream.ase.component.datamanager; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import com.codesdream.ase.component.json.respond.JSONBaseRespondObject; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; @@ -19,7 +21,13 @@ public class JSONParameter { // 处理Request Body public String getRequestBody(HttpServletRequest request){ try { - return request.getParameter("json"); + StringBuilder stringBuilder = new StringBuilder(); + BufferedReader reader = request.getReader(); + reader.reset(); + String line; + while ((line = reader.readLine()) != null) + stringBuilder.append(line); + return stringBuilder.toString(); } catch (Exception e) { e.printStackTrace(); return null; @@ -50,6 +58,38 @@ public class JSONParameter { return JSON.toJSONString(object); } + // 根据对象构造获得标准的JSON响应字符串返回 + public String getJSONStandardRespond(Integer status, String msg, Object dataObject){ + JSONBaseRespondObject respondObject = new JSONBaseRespondObject(status, msg); + respondObject.setData(dataObject); + return getJSONString(respondObject); + } + + // 获得标准的JSON响应字符串返回(404状态) + public String getJSONStandardRespond404(String msg){ + JSONBaseRespondObject respondObject = new JSONBaseRespondObject(404, msg); + return getJSONString(respondObject); + } + + // 获得标准的JSON响应字符串返回(500状态) + public String getJSONStandardRespond500(String msg){ + JSONBaseRespondObject respondObject = new JSONBaseRespondObject(500, msg); + return getJSONString(respondObject); + } + + // 获得标准的JSON响应字符串返回(200状态) + public String getJSONStandardRespond200(Object dataObject){ + JSONBaseRespondObject respondObject = new JSONBaseRespondObject(200, "ok"); + respondObject.setData(dataObject); + return getJSONString(respondObject); + } + + // 获得标准的JSON响应字符串返回(403状态) + public String getJSONStandardRespond403(){ + JSONBaseRespondObject respondObject = new JSONBaseRespondObject(403, "forbidden"); + return getJSONString(respondObject); + } + // 由JSON对象获得对应的Java对象 public T getJavaObject(JSONObject json, Class type){ return json.toJavaObject(type); @@ -61,4 +101,6 @@ public class JSONParameter { return json.map(jsonObject -> getJavaObject(jsonObject, type)); } + + } diff --git a/src/main/java/com/codesdream/ase/component/json/JSONBaseObject.java b/src/main/java/com/codesdream/ase/component/json/JSONBaseObject.java new file mode 100644 index 0000000..86504b4 --- /dev/null +++ b/src/main/java/com/codesdream/ase/component/json/JSONBaseObject.java @@ -0,0 +1,12 @@ +package com.codesdream.ase.component.json; + +import lombok.extern.slf4j.Slf4j; + +import java.util.Date; + +// 所有有效的JSON对象模板 +@Slf4j +public class JSONBaseObject { + Date time = new Date(); + +} diff --git a/src/main/java/com/codesdream/ase/component/json/request/JSONBaseRequestObject.java b/src/main/java/com/codesdream/ase/component/json/request/JSONBaseRequestObject.java new file mode 100644 index 0000000..910ee53 --- /dev/null +++ b/src/main/java/com/codesdream/ase/component/json/request/JSONBaseRequestObject.java @@ -0,0 +1,13 @@ +package com.codesdream.ase.component.json.request; + +import com.codesdream.ase.component.json.JSONBaseObject; +import lombok.extern.slf4j.Slf4j; + +import java.util.Date; + +// 客户端请求的JSON模板 +@Slf4j +public class JSONBaseRequestObject extends JSONBaseObject { + String signed; + +} diff --git a/src/main/java/com/codesdream/ase/component/json/request/UserLoginChecker.java b/src/main/java/com/codesdream/ase/component/json/request/UserLoginChecker.java index ac0c738..0f706cf 100644 --- a/src/main/java/com/codesdream/ase/component/json/request/UserLoginChecker.java +++ b/src/main/java/com/codesdream/ase/component/json/request/UserLoginChecker.java @@ -4,7 +4,10 @@ import lombok.Data; @Data public class UserLoginChecker { + // 请求类型 private String checkType; private String username; private String password; + private String clientCode; + } diff --git a/src/main/java/com/codesdream/ase/component/json/respond/EmptyDataObjectRespond.java b/src/main/java/com/codesdream/ase/component/json/respond/EmptyDataObjectRespond.java new file mode 100644 index 0000000..e3ae905 --- /dev/null +++ b/src/main/java/com/codesdream/ase/component/json/respond/EmptyDataObjectRespond.java @@ -0,0 +1,5 @@ +package com.codesdream.ase.component.json.respond; + +public class EmptyDataObjectRespond { + +} diff --git a/src/main/java/com/codesdream/ase/component/json/respond/FailedSONRespond.java b/src/main/java/com/codesdream/ase/component/json/respond/FailedSONRespond.java deleted file mode 100644 index e0c6de7..0000000 --- a/src/main/java/com/codesdream/ase/component/json/respond/FailedSONRespond.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.codesdream.ase.component.json.respond; - -// 请求失败返回JSON -public class FailedSONRespond extends JSONBaseRespondObject { - public FailedSONRespond(){ - super(); - this.status = "fail"; - } -} diff --git a/src/main/java/com/codesdream/ase/component/json/respond/JSONBaseRespondObject.java b/src/main/java/com/codesdream/ase/component/json/respond/JSONBaseRespondObject.java index a441bd5..f180023 100644 --- a/src/main/java/com/codesdream/ase/component/json/respond/JSONBaseRespondObject.java +++ b/src/main/java/com/codesdream/ase/component/json/respond/JSONBaseRespondObject.java @@ -1,22 +1,58 @@ package com.codesdream.ase.component.json.respond; +import com.codesdream.ase.component.json.JSONBaseObject; import lombok.Data; +import lombok.EqualsAndHashCode; import java.util.Date; -// 服务端返回的JSON对象基础信息 +// 服务端返回的JSON对象模板 +@EqualsAndHashCode(callSuper = true) @Data -public class JSONBaseRespondObject { - // 请求成功状态 - String status = "fail"; - // 时间 - Date time = new Date(); +public class JSONBaseRespondObject extends JSONBaseObject { - public JSONBaseRespondObject(){ + // 存放返回内容 + private Object data = new EmptyDataObjectRespond(); + // 存放响应信息提示 + private String msg = ""; + + // 状态 + private Integer status = 200; + + public JSONBaseRespondObject(String msg){ + super(); + this.status = 200; + this.msg = msg; } - public JSONBaseRespondObject(String status){ + public JSONBaseRespondObject(Integer status, String msg){ + super(); this.status = status; + this.msg = msg; + } + + public void setStatusNotFound(){ + this.status = 404; + } + + public void setStatusBadRequest(){ + this.status = 400; + } + + public void setStatusUnauthorized(){ + this.status = 401; + } + + public void setStatusForbidden(){ + this.status = 403; + } + + public void setStatusInternalServerError(){ + this.status = 500; + } + + public void setStatusOK(){ + this.status = 200; } } diff --git a/src/main/java/com/codesdream/ase/component/json/respond/JSONStandardFailedRespond.java b/src/main/java/com/codesdream/ase/component/json/respond/JSONStandardFailedRespond.java new file mode 100644 index 0000000..ba1626f --- /dev/null +++ b/src/main/java/com/codesdream/ase/component/json/respond/JSONStandardFailedRespond.java @@ -0,0 +1,8 @@ +package com.codesdream.ase.component.json.respond; + +// 请求失败返回JSON +public class JSONStandardFailedRespond extends JSONBaseRespondObject { + public JSONStandardFailedRespond(){ + super(500, "failed"); + } +} diff --git a/src/main/java/com/codesdream/ase/component/json/respond/UserLoginCheckerJSONRespond.java b/src/main/java/com/codesdream/ase/component/json/respond/UserLoginCheckerJSONRespond.java index bbd291c..79ccfe9 100644 --- a/src/main/java/com/codesdream/ase/component/json/respond/UserLoginCheckerJSONRespond.java +++ b/src/main/java/com/codesdream/ase/component/json/respond/UserLoginCheckerJSONRespond.java @@ -3,17 +3,12 @@ package com.codesdream.ase.component.json.respond; import lombok.Data; import lombok.EqualsAndHashCode; -@EqualsAndHashCode(callSuper = true) @Data -public class UserLoginCheckerJSONRespond extends JSONBaseRespondObject { +public class UserLoginCheckerJSONRespond { boolean userExist = false; boolean loginStatus = false; boolean userBanned = false; String respondInformation = ""; - String sessionId = ""; - - public UserLoginCheckerJSONRespond(){ - super("success"); - } + String token = ""; } diff --git a/src/main/java/com/codesdream/ase/component/json/respond/UserLoginTokenJSONRespond.java b/src/main/java/com/codesdream/ase/component/json/respond/UserLoginTokenJSONRespond.java new file mode 100644 index 0000000..43272ab --- /dev/null +++ b/src/main/java/com/codesdream/ase/component/json/respond/UserLoginTokenJSONRespond.java @@ -0,0 +1,6 @@ +package com.codesdream.ase.component.json.respond; + +// 返回登录后的授权码(用于客户端生成签名) +public class UserLoginTokenJSONRespond { + String token; +} diff --git a/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationEntryPoint.java b/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationEntryPoint.java index caf783c..3e62a3f 100644 --- a/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationEntryPoint.java +++ b/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationEntryPoint.java @@ -1,6 +1,7 @@ package com.codesdream.ase.component.permission; import com.codesdream.ase.component.datamanager.JSONParameter; +import com.codesdream.ase.component.json.respond.JSONBaseRespondObject; import com.codesdream.ase.component.json.respond.UserLoginCheckerJSONRespond; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.AuthenticationException; @@ -23,18 +24,8 @@ public class ASEAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { - log.info("ASEAuthenticationEntryPoint Found!"); - - response.setCharacterEncoding("utf-8"); - response.setContentType("text/javascript;charset=utf-8"); - UserLoginCheckerJSONRespond checkerRespond = new UserLoginCheckerJSONRespond(); - checkerRespond.setLoginStatus(false); - checkerRespond.setUserExist(false); - checkerRespond.setUserBanned(true); - checkerRespond.setRespondInformation("Anonymous user has no access to this resource"); - - // 对匿名用户返回 - response.getWriter().print(jsonParameter.getJSONString(checkerRespond)); + // 对匿名用户返回403 + response.getWriter().print(jsonParameter.getJSONStandardRespond403()); } } diff --git a/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationFailureHandler.java b/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationFailureHandler.java index cf200cb..1680ec3 100644 --- a/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationFailureHandler.java +++ b/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationFailureHandler.java @@ -25,7 +25,7 @@ public class ASEAuthenticationFailureHandler extends SimpleUrlAuthenticationFail public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { - log.info("ASEAuthenticationSuccessHandler Login Fail!"); + log.info("ASEAuthenticationFailureHandler Login Fail!"); UserLoginCheckerJSONRespond respond = new UserLoginCheckerJSONRespond(); respond.setUserExist(false); respond.setLoginStatus(false); @@ -33,6 +33,6 @@ public class ASEAuthenticationFailureHandler extends SimpleUrlAuthenticationFail respond.setRespondInformation("Authentication Failed"); // 填充response对象 - response.getWriter().write(jsonParameter.getJSONString(respond)); + response.getWriter().write(jsonParameter.getJSONStandardRespond200(respond)); } } diff --git a/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationSuccessHandler.java b/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationSuccessHandler.java index 85543fd..9abed5f 100644 --- a/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationSuccessHandler.java +++ b/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationSuccessHandler.java @@ -1,14 +1,17 @@ package com.codesdream.ase.component.permission; +import com.codesdream.ase.component.auth.JSONTokenAuthenticationToken; import com.codesdream.ase.component.datamanager.JSONParameter; import com.codesdream.ase.component.json.respond.UserLoginCheckerJSONRespond; import com.codesdream.ase.model.permission.User; + +import com.codesdream.ase.service.IAuthService; import lombok.extern.slf4j.Slf4j; + import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; + import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; -import org.springframework.security.web.authentication.WebAuthenticationDetails; + import org.springframework.stereotype.Component; import javax.annotation.Resource; @@ -26,31 +29,33 @@ public class ASEAuthenticationSuccessHandler extends SavedRequestAwareAuthentica @Resource private JSONParameter jsonParameter; + @Resource + private IAuthService authService; + @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - // 对AJAX登录请求特殊化处理 - if(Optional.ofNullable(request.getHeader("X-Requested-With")).isPresent()) { - HttpSession session = request.getSession(); - SecurityContext securityContext = SecurityContextHolder.getContext(); - - session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext); - } - - // 打印用户登录成功日志 - log.info(String.format("ASEAuthenticationSuccessHandler: %s Login Success.", - ((User)authentication.getDetails()).getUsername())); UserLoginCheckerJSONRespond respond = new UserLoginCheckerJSONRespond(); respond.setUserExist(authentication.isAuthenticated()); respond.setLoginStatus(authentication.isAuthenticated()); + respond.setRespondInformation("Authentication Success"); - // 获得session id - WebAuthenticationDetails webAuthenticationDetails = (WebAuthenticationDetails) (authentication.getDetails()); - respond.setSessionId(webAuthenticationDetails.getSessionId()); + // 获得 JSONTokenAuthenticationToken + JSONTokenAuthenticationToken authenticationToken = (JSONTokenAuthenticationToken) authentication; - response.getWriter().write(jsonParameter.getJSONString(respond)); + User user = (User) authenticationToken.getPrincipal(); + + Optional tokenOptional = authService.userNewTokenGetter( + user.getUsername(), authenticationToken.getClientCode()); + + if(tokenOptional.isPresent()){ + respond.setToken(tokenOptional.get()); + } + else respond.setToken(""); + + response.getWriter().write(jsonParameter.getJSONStandardRespond200(respond)); } } diff --git a/src/main/java/com/codesdream/ase/component/permission/ASEJSONTokenAuthenticationFilter.java b/src/main/java/com/codesdream/ase/component/permission/ASEJSONTokenAuthenticationFilter.java new file mode 100644 index 0000000..a518944 --- /dev/null +++ b/src/main/java/com/codesdream/ase/component/permission/ASEJSONTokenAuthenticationFilter.java @@ -0,0 +1,119 @@ +package com.codesdream.ase.component.permission; + +import com.codesdream.ase.component.auth.AJAXRequestChecker; +import com.codesdream.ase.component.auth.JSONRandomCodeGenerator; +import com.codesdream.ase.component.auth.JSONSignedGenerator; +import com.codesdream.ase.component.auth.JSONTokenAuthenticationToken; +import com.codesdream.ase.component.datamanager.JSONParameter; +import com.codesdream.ase.model.auth.JSONToken; +import com.codesdream.ase.service.AuthService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.annotation.Resource; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Collection; +import java.util.Date; +import java.util.Optional; + + +// API请求验证过滤 +@Slf4j +public class ASEJSONTokenAuthenticationFilter extends OncePerRequestFilter { + + @Resource + private JSONParameter jsonParameter; + + @Resource + private JSONRandomCodeGenerator randomCodeGenerator; + + @Resource + private AJAXRequestChecker ajaxRequestChecker; + + @Resource + private AuthService authService; + + @Resource + private JSONSignedGenerator signedGenerator; + + @Resource + private UserDetailsService userDetailsService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + // 用户名 + String username = request.getHeader( "username"); + // 客户端签名 + String signed = request.getHeader("signed"); + // 时间戳 + String timestamp = request.getHeader("timestamp"); + + if (signed != null && username != null && timestamp != null) { + // 获得具体时间 + Date date = new Date(Long.parseLong(timestamp)); + + Date now = new Date(); + + // 限制时间戳有效区间为60s + long dtTime = 60*1000; + Date maxDate = new Date(now.getTime() + dtTime); + + // 检查时间戳是否合理 + if(maxDate.after(date)) { + // 从服务器中查找token + Optional optionalJSONToken = authService.findTokenByUserName(username); + if (optionalJSONToken.isPresent()) { + JSONToken token = optionalJSONToken.get(); + + // 检查token是否过期 + if (!authService.checkTokenIfExpired(token)) { + // 生成特征随机代码 + String randomCode = randomCodeGenerator.generateRandomCode(username, date, token.getClientCode()); + + log.info(String.format("Determined Signed: %s", + signedGenerator.generateSigned(username, randomCode, token.getToken()))); + + // 检查签名是否正确 + if (signed.equals(signedGenerator.generateSigned(username, randomCode, token.getToken()))) { + // 执行授权操作 + doAuthentication(username, request); + } + } + } + } + } + + filterChain.doFilter(request, response); + + } + + // 执行授权 + private void doAuthentication(String username, HttpServletRequest request){ + // 查询用户的相关信息 + UserDetails user = userDetailsService.loadUserByUsername(username); + + // 生成用户权限列表 + Collection authorities = user.getAuthorities(); + + // 生成授权柄 (储存上下文信息) + JSONTokenAuthenticationToken authentication = + new JSONTokenAuthenticationToken(user, null, authorities); + + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + // 执行授权 + SecurityContextHolder.getContext().setAuthentication(authentication); + } + +} diff --git a/src/main/java/com/codesdream/ase/component/permission/ASESecurityAuthenticationProvider.java b/src/main/java/com/codesdream/ase/component/permission/ASESecurityAuthenticationProvider.java index 8e9dcf3..7ba70ca 100644 --- a/src/main/java/com/codesdream/ase/component/permission/ASESecurityAuthenticationProvider.java +++ b/src/main/java/com/codesdream/ase/component/permission/ASESecurityAuthenticationProvider.java @@ -1,5 +1,7 @@ package com.codesdream.ase.component.permission; +import com.codesdream.ase.component.auth.JSONTokenAuthenticationToken; +import com.codesdream.ase.component.auth.JSONTokenUsernamePasswordAuthenticationToken; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.*; import org.springframework.security.core.Authentication; @@ -14,6 +16,7 @@ import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.Collection; +// 普通用户名密码验证, 用户获得Token @Slf4j @Component public class ASESecurityAuthenticationProvider implements AuthenticationProvider { @@ -28,10 +31,15 @@ public class ASESecurityAuthenticationProvider implements AuthenticationProvider @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { + JSONTokenUsernamePasswordAuthenticationToken authenticationToken = + (JSONTokenUsernamePasswordAuthenticationToken) authentication; + // 获得登录表单中的学号 - String username = usernameEncoder.encode(authentication.getName()); + String username = usernameEncoder.encode((CharSequence) authenticationToken.getPrincipal()); // 获得表单中的密码 - String password = passwordEncoder.encode(authentication.getCredentials().toString()); + String password = passwordEncoder.encode((CharSequence) authenticationToken.getCredentials()); + // 获得 + String clientCode = authenticationToken.getClientCode(); // 判断用户是否存在 UserDetails userInfo = userDetailsService.loadUserByUsername(username); @@ -57,12 +65,14 @@ public class ASESecurityAuthenticationProvider implements AuthenticationProvider throw new AccountExpiredException("User IS Expired"); } + // 生成权限列表 Collection authorities = userInfo.getAuthorities(); - return new UsernamePasswordAuthenticationToken(userInfo, password, authorities); + + return new JSONTokenAuthenticationToken(userInfo, clientCode, authorities); } @Override public boolean supports(Class aClass) { - return true; + return aClass.equals(JSONTokenUsernamePasswordAuthenticationToken.class); } } diff --git a/src/main/java/com/codesdream/ase/component/permission/ASEUsernamePasswordAuthenticationFilter.java b/src/main/java/com/codesdream/ase/component/permission/ASEUsernamePasswordAuthenticationFilter.java index a9e71c1..2be84dd 100644 --- a/src/main/java/com/codesdream/ase/component/permission/ASEUsernamePasswordAuthenticationFilter.java +++ b/src/main/java/com/codesdream/ase/component/permission/ASEUsernamePasswordAuthenticationFilter.java @@ -1,5 +1,7 @@ package com.codesdream.ase.component.permission; +import com.codesdream.ase.component.auth.AJAXRequestChecker; +import com.codesdream.ase.component.auth.JSONTokenUsernamePasswordAuthenticationToken; import com.codesdream.ase.component.datamanager.JSONParameter; import com.codesdream.ase.component.json.request.UserLoginChecker; import lombok.extern.slf4j.Slf4j; @@ -9,6 +11,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; @@ -22,41 +25,42 @@ public class ASEUsernamePasswordAuthenticationFilter extends UsernamePasswordAut @Resource private JSONParameter jsonParameter; + @Resource + private AJAXRequestChecker ajaxRequestChecker; + @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { // 判断是否为AJAX请求格式的数据 - if(Optional.ofNullable(request.getHeader("X-Requested-With")).isPresent()) { - - Optional checker = jsonParameter.getJavaObjectByRequest(request, UserLoginChecker.class); - if(!checker.isPresent()) throw new BadCredentialsException("Invalid AJAX JSON Request"); - if (!checker.get().getCheckType().equals("From")) - throw new AuthenticationServiceException("Invalid Checker Type"); - - // 获得相应的用户名密码 - String username = checker.get().getUsername(); - String password = checker.get().getPassword(); - - if (username == null) username = ""; - if (password == null) password = ""; - - // 去除首尾两端的空白字符 - username = username.trim(); - password = password.trim(); - - UsernamePasswordAuthenticationToken authRequest = - new UsernamePasswordAuthenticationToken(username, password); - - - log.info(String.format("User AJAX JSON Authentication: %s %s.", username, password)); - - setDetails(request, authRequest); - - return this.getAuthenticationManager().authenticate(authRequest); - } - else{ - return super.attemptAuthentication(request, response); + if(!ajaxRequestChecker.checkAjaxPOSTRequest(request)) { + throw new AuthenticationServiceException("Authentication method not supported: NOT Ajax Method."); } + + Optional checker = jsonParameter.getJavaObjectByRequest(request, UserLoginChecker.class); + if(!checker.isPresent()) throw new BadCredentialsException("Invalid AJAX JSON Request"); + + if (!checker.get().getCheckType().equals("UsernamePasswordChecker")) + throw new AuthenticationServiceException("Authentication not supported: NOT Username Password Type."); + + // 获得相应的用户名密码 + String username = checker.get().getUsername(); + String password = checker.get().getPassword(); + String clientCode = checker.get().getClientCode(); + + if (username == null) username = ""; + if (password == null) password = ""; + + // 去除首尾两端的空白字符 + username = username.trim(); + password = password.trim(); + + + JSONTokenUsernamePasswordAuthenticationToken authRequest = + new JSONTokenUsernamePasswordAuthenticationToken(username, password, clientCode); + + authRequest.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + return this.getAuthenticationManager().authenticate(authRequest); } } diff --git a/src/main/java/com/codesdream/ase/configure/CustomWebSecurityConfig.java b/src/main/java/com/codesdream/ase/configure/CustomWebSecurityConfig.java index 9659196..1bb0f98 100644 --- a/src/main/java/com/codesdream/ase/configure/CustomWebSecurityConfig.java +++ b/src/main/java/com/codesdream/ase/configure/CustomWebSecurityConfig.java @@ -4,6 +4,7 @@ import com.codesdream.ase.component.permission.*; import com.codesdream.ase.service.ASEUserDetailsService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; @@ -51,6 +52,7 @@ public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter { @Resource ASEAccessDeniedHandler accessDeniedHandler; + @Override protected void configure(HttpSecurity http) throws Exception { http @@ -66,7 +68,9 @@ public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter { // 替换掉原有的UsernamePasswordAuthenticationFilter http.addFilterAt(aseUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) - .addFilterAfter(new SecurityContextPersistenceFilter(), UsernamePasswordAuthenticationFilter.class); + .addFilterBefore(asejsonTokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); + + http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } @@ -90,6 +94,12 @@ public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter { "/login/**"); } + //注册自定义的UsernamePasswordAuthenticationFilter + @Bean + ASEJSONTokenAuthenticationFilter asejsonTokenAuthenticationFilter() throws Exception { + return new ASEJSONTokenAuthenticationFilter(); + } + //注册自定义的UsernamePasswordAuthenticationFilter @Bean ASEUsernamePasswordAuthenticationFilter aseUsernamePasswordAuthenticationFilter() throws Exception { @@ -99,7 +109,7 @@ public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter { filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy(sessionRegistry())); filter.setAllowSessionCreation(true); filter.setRequiresAuthenticationRequestMatcher( - new AntPathRequestMatcher("/login/process", "POST")); + new AntPathRequestMatcher("/login/token", "POST")); filter.setAuthenticationManager(authenticationManagerBean()); return filter; diff --git a/src/main/java/com/codesdream/ase/controller/APIController.java b/src/main/java/com/codesdream/ase/controller/APIController.java new file mode 100644 index 0000000..f2a574d --- /dev/null +++ b/src/main/java/com/codesdream/ase/controller/APIController.java @@ -0,0 +1,14 @@ +package com.codesdream.ase.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api") +public class APIController { + + @RequestMapping("hello") + String hello(){ + return "hello"; + } +} diff --git a/src/main/java/com/codesdream/ase/controller/ASEErrorController.java b/src/main/java/com/codesdream/ase/controller/ASEErrorController.java index d5930ad..124294f 100644 --- a/src/main/java/com/codesdream/ase/controller/ASEErrorController.java +++ b/src/main/java/com/codesdream/ase/controller/ASEErrorController.java @@ -18,6 +18,7 @@ import java.util.List; @Controller public class ASEErrorController implements ErrorController { + @RequestMapping("/error") public String handleError(HttpServletRequest request, Model model){ Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); diff --git a/src/main/java/com/codesdream/ase/controller/LoginController.java b/src/main/java/com/codesdream/ase/controller/LoginController.java index 33fb19f..db99311 100644 --- a/src/main/java/com/codesdream/ase/controller/LoginController.java +++ b/src/main/java/com/codesdream/ase/controller/LoginController.java @@ -2,7 +2,7 @@ package com.codesdream.ase.controller; import com.alibaba.fastjson.JSONObject; import com.codesdream.ase.component.datamanager.JSONParameter; -import com.codesdream.ase.component.json.respond.FailedSONRespond; +import com.codesdream.ase.component.json.respond.JSONStandardFailedRespond; import com.codesdream.ase.component.json.respond.JSONBaseRespondObject; import com.codesdream.ase.component.permission.ASEUsernameEncoder; import com.codesdream.ase.component.json.request.UserLoginChecker; @@ -41,20 +41,21 @@ public class LoginController { return "login"; } - @RequestMapping(value = "/login/check", method = RequestMethod.POST) + @RequestMapping(value = "/login/check_exists", method = RequestMethod.POST) @ResponseBody - String checkLogin(HttpServletRequest request){ + String checkExists(HttpServletRequest request){ // 检查是否为JSON Optional json = jsonParameter.getJSONByRequest(request); - if(!json.isPresent()) return jsonParameter.getJSONString(new FailedSONRespond()); + if(!json.isPresent()) return jsonParameter.getJSONString(new JSONStandardFailedRespond()); UserLoginChecker loginChecker = json.get().toJavaObject(UserLoginChecker.class); - // 检查类型 + + // 检查学号对应的用户名是否存在 if(loginChecker.getCheckType().equals("UsernameExistChecker")){ // 根据学号计算用户名 - String user = usernameEncoder.encode(loginChecker.getUsername()) ; + String user = userService.getUsernameByStudentId(loginChecker.getUsername()); // 查询用户名存在状态 boolean existStatus = userService.checkIfUserExists(user).getKey(); // 构造返回对象 @@ -64,8 +65,33 @@ public class LoginController { } else { // 返回失败对象 - return jsonParameter.getJSONString(new JSONBaseRespondObject()); + return jsonParameter.getJSONString(new JSONStandardFailedRespond()); } } + // 根据学号计算对应的username + @RequestMapping(value = "/login/check_uid", method = RequestMethod.POST) + @ResponseBody + String checkUsernameByStudentID(HttpServletRequest request){ + // 检查是否为JSON + Optional json = jsonParameter.getJSONByRequest(request); + if(!json.isPresent()) return jsonParameter.getJSONString(new JSONStandardFailedRespond()); + + UserLoginChecker loginChecker = json.get().toJavaObject(UserLoginChecker.class); + + if(loginChecker.getCheckType().equals("UIDGeneratorChecker")) { + UserLoginCheckerJSONRespond respond = new UserLoginCheckerJSONRespond(); + respond.setRespondInformation(userService.getUsernameByStudentId(loginChecker.getUsername())); + return jsonParameter.getJSONString(respond); + } + else { + // 返回失败对象 + return jsonParameter.getJSONString(new JSONStandardFailedRespond()); + } + + + } + + + } diff --git a/src/main/java/com/codesdream/ase/exception/JSONTokenExpiredException.java b/src/main/java/com/codesdream/ase/exception/JSONTokenExpiredException.java new file mode 100644 index 0000000..8c90908 --- /dev/null +++ b/src/main/java/com/codesdream/ase/exception/JSONTokenExpiredException.java @@ -0,0 +1,9 @@ +package com.codesdream.ase.exception; + +import org.springframework.security.core.AuthenticationException; + +public class JSONTokenExpiredException extends AuthenticationException { + public JSONTokenExpiredException(String msg) { + super(msg); + } +} diff --git a/src/main/java/com/codesdream/ase/exception/JSONTokenIncorrectSignedException.java b/src/main/java/com/codesdream/ase/exception/JSONTokenIncorrectSignedException.java new file mode 100644 index 0000000..edef8c0 --- /dev/null +++ b/src/main/java/com/codesdream/ase/exception/JSONTokenIncorrectSignedException.java @@ -0,0 +1,10 @@ +package com.codesdream.ase.exception; + +import org.springframework.security.core.AuthenticationException; + +public class JSONTokenIncorrectSignedException extends AuthenticationException { + + public JSONTokenIncorrectSignedException(String msg) { + super(msg); + } +} diff --git a/src/main/java/com/codesdream/ase/model/auth/JSONToken.java b/src/main/java/com/codesdream/ase/model/auth/JSONToken.java new file mode 100644 index 0000000..751ebf1 --- /dev/null +++ b/src/main/java/com/codesdream/ase/model/auth/JSONToken.java @@ -0,0 +1,31 @@ +package com.codesdream.ase.model.auth; + +import lombok.Data; + +import javax.persistence.*; +import java.util.Date; + +// token记录 +@Data +@Entity +@Table(name = "json_tokens") +public class JSONToken { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + // token对应的用户 + @Column(unique = true) + private String username; + + // token值 + @Column(unique = true) + private String token; + + // 客户端标识口令 + private String clientCode; + + // token过期时间 + private Date expiredDate; +} diff --git a/src/main/java/com/codesdream/ase/repository/auth/JSONTokenRepository.java b/src/main/java/com/codesdream/ase/repository/auth/JSONTokenRepository.java new file mode 100644 index 0000000..a2a4f68 --- /dev/null +++ b/src/main/java/com/codesdream/ase/repository/auth/JSONTokenRepository.java @@ -0,0 +1,12 @@ +package com.codesdream.ase.repository.auth; + +import com.codesdream.ase.model.auth.JSONToken; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface JSONTokenRepository extends CrudRepository { + Optional findByUsername(String username); +} diff --git a/src/main/java/com/codesdream/ase/service/AuthService.java b/src/main/java/com/codesdream/ase/service/AuthService.java new file mode 100644 index 0000000..1784365 --- /dev/null +++ b/src/main/java/com/codesdream/ase/service/AuthService.java @@ -0,0 +1,63 @@ +package com.codesdream.ase.service; + +import com.codesdream.ase.component.auth.AuthTokenGenerator; +import com.codesdream.ase.model.auth.JSONToken; +import com.codesdream.ase.model.permission.User; +import com.codesdream.ase.repository.auth.JSONTokenRepository; +import com.sun.org.apache.xpath.internal.operations.Bool; +import javafx.util.Pair; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.Optional; + +// 认证服务 +@Service +public class AuthService implements IAuthService { + + @Resource + private JSONTokenRepository jsonTokenRepository; + + @Resource + private IUserService userService; + + @Resource + private AuthTokenGenerator authTokenGenerator; + + @Override + public Optional findTokenByUserName(String username) { + return jsonTokenRepository.findByUsername(username); + } + + @Override + public boolean checkTokenIfExpired(JSONToken token) { + return token.getExpiredDate().compareTo(new Date()) <= 0; + } + + @Override + public Optional userNewTokenGetter(String username, String clientCode) { + Pair userPair = userService.checkIfUserExists(username); + if(userPair.getKey()){ + Optional jsonTokenOptional = jsonTokenRepository.findByUsername(username); + JSONToken token = jsonTokenOptional.orElseGet(JSONToken::new); + + // 过期时间设置为三十分钟后 + long currentTime = System.currentTimeMillis(); + currentTime +=30*60*1000; + token.setExpiredDate(new Date(currentTime)); + token.setToken(authTokenGenerator.generateAuthToken(username)); + + + // 设置用户名 + token.setUsername(username); + // 设置客户端代号 + token.setClientCode(clientCode); + + // 在数据库中更新新的token + token = jsonTokenRepository.save(token); + return Optional.ofNullable(token.getToken()); + } + else return Optional.empty(); + } +} diff --git a/src/main/java/com/codesdream/ase/service/IAuthService.java b/src/main/java/com/codesdream/ase/service/IAuthService.java new file mode 100644 index 0000000..fc4eae0 --- /dev/null +++ b/src/main/java/com/codesdream/ase/service/IAuthService.java @@ -0,0 +1,16 @@ +package com.codesdream.ase.service; + +import com.codesdream.ase.model.auth.JSONToken; + +import java.util.Optional; + +public interface IAuthService { + // 通过用户名查找与对应用户相关联的token + Optional findTokenByUserName(String username); + + // 检查token是否过期 + boolean checkTokenIfExpired(JSONToken token); + + // 为用户获得一个新的API Token + Optional userNewTokenGetter(String username, String clientCode); +} diff --git a/src/main/java/com/codesdream/ase/service/IUserService.java b/src/main/java/com/codesdream/ase/service/IUserService.java index 8bfe3fa..06281f4 100644 --- a/src/main/java/com/codesdream/ase/service/IUserService.java +++ b/src/main/java/com/codesdream/ase/service/IUserService.java @@ -24,6 +24,7 @@ public interface IUserService { User findUserByUsername(String username); + // 查询用户是否存在 public Pair checkIfUserExists(String username); // 获得用户所有的权限角色 @@ -38,6 +39,9 @@ public interface IUserService { // 根据学号生成随机用户名 void generateRandomUsernameByStudentID(User user, String id); + // 更具学号获得对应的用户名 + String getUsernameByStudentId(String studentId); + // 随机生成一个用户名 void generateRandomUsername(User user); diff --git a/src/main/java/com/codesdream/ase/service/UserService.java b/src/main/java/com/codesdream/ase/service/UserService.java index e621b51..e09ad09 100644 --- a/src/main/java/com/codesdream/ase/service/UserService.java +++ b/src/main/java/com/codesdream/ase/service/UserService.java @@ -81,6 +81,11 @@ public class UserService implements IUserService { user.setUsername(usernameEncoder.encode(id)); } + @Override + public String getUsernameByStudentId(String studentId) { + return usernameEncoder.encode(studentId); + } + @Override public void generateRandomUsername(User user) { user.setUsername(usernameEncoder.encode(UUID.randomUUID().toString()));