From 2da6f6f8f84aee5e3b442b52a2fe19b1841906be Mon Sep 17 00:00:00 2001 From: Saturneric Date: Sun, 15 Mar 2020 19:18:51 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E7=A6=81=E7=94=A8Session;=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0API=20Token=E6=8E=88=E6=9D=83=E4=B8=8E=E9=AA=8C?= =?UTF-8?q?=E8=AF=81;=20=E9=87=8D=E5=86=99=E4=B8=8E=E8=B0=83=E6=95=B4Sprin?= =?UTF-8?q?g=20Security=E7=9A=84=E4=B8=80=E4=BA=9B=E7=BB=84=E4=BB=B6;=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AE=A4=E8=AF=81=E6=9C=8D=E5=8A=A1(Auth)?= =?UTF-8?q?=E5=B1=82;=20=E6=B7=BB=E5=8A=A0JSON=20Token=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E5=B1=82;=20=E5=AE=8C=E5=96=84=E7=94=A8=E6=88=B7=E5=B1=82;=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=80=E4=BA=9B=E4=B8=8EAPI=20Token?= =?UTF-8?q?=E6=8E=88=E6=9D=83=E4=B8=8E=E8=AE=A4=E8=AF=81=E6=9C=89=E5=85=B3?= =?UTF-8?q?=E7=9A=84Exception;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/auth/AJAXRequestChecker.java | 14 +++ .../component/auth/AuthTokenGenerator.java | 19 +++ .../auth/JSONRandomCodeGenerator.java | 18 +++ .../component/auth/JSONSignedGenerator.java | 16 +++ .../auth/JSONTokenAuthenticationToken.java | 58 +++++++++ .../ase/component/auth/SHA1Encoder.java | 17 +++ .../component/datamanager/JSONParameter.java | 8 +- .../ase/component/json/JSONBaseObject.java | 12 ++ .../json/request/JSONBaseRequestObject.java | 13 ++ .../json/request/UserLoginChecker.java | 6 + .../json/respond/JSONBaseRespondObject.java | 7 +- .../respond/UserLoginCheckerJSONRespond.java | 2 +- .../respond/UserLoginTokenJSONRespond.java | 6 + .../ASEAuthenticationFailureHandler.java | 2 +- .../ASEAuthenticationSuccessHandler.java | 27 ++-- .../ASEJSONTokenAuthenticationFilter.java | 119 ++++++++++++++++++ .../ASESecurityAuthenticationProvider.java | 3 +- ...EUsernamePasswordAuthenticationFilter.java | 65 +++++----- .../configure/CustomWebSecurityConfig.java | 14 ++- .../ase/controller/APIController.java | 14 +++ .../ase/controller/ASEErrorController.java | 1 + .../ase/controller/LoginController.java | 35 +++++- .../exception/JSONTokenExpiredException.java | 9 ++ .../JSONTokenIncorrectSignedException.java | 10 ++ .../codesdream/ase/model/auth/JSONToken.java | 28 +++++ .../repository/auth/JSONTokenRepository.java | 12 ++ .../codesdream/ase/service/AuthService.java | 57 +++++++++ .../codesdream/ase/service/IAuthService.java | 16 +++ .../codesdream/ase/service/IUserService.java | 4 + .../codesdream/ase/service/UserService.java | 5 + 30 files changed, 564 insertions(+), 53 deletions(-) create mode 100644 src/main/java/com/codesdream/ase/component/auth/AJAXRequestChecker.java create mode 100644 src/main/java/com/codesdream/ase/component/auth/AuthTokenGenerator.java create mode 100644 src/main/java/com/codesdream/ase/component/auth/JSONRandomCodeGenerator.java create mode 100644 src/main/java/com/codesdream/ase/component/auth/JSONSignedGenerator.java create mode 100644 src/main/java/com/codesdream/ase/component/auth/JSONTokenAuthenticationToken.java create mode 100644 src/main/java/com/codesdream/ase/component/auth/SHA1Encoder.java create mode 100644 src/main/java/com/codesdream/ase/component/json/JSONBaseObject.java create mode 100644 src/main/java/com/codesdream/ase/component/json/request/JSONBaseRequestObject.java create mode 100644 src/main/java/com/codesdream/ase/component/json/respond/UserLoginTokenJSONRespond.java create mode 100644 src/main/java/com/codesdream/ase/component/permission/ASEJSONTokenAuthenticationFilter.java create mode 100644 src/main/java/com/codesdream/ase/controller/APIController.java create mode 100644 src/main/java/com/codesdream/ase/exception/JSONTokenExpiredException.java create mode 100644 src/main/java/com/codesdream/ase/exception/JSONTokenIncorrectSignedException.java create mode 100644 src/main/java/com/codesdream/ase/model/auth/JSONToken.java create mode 100644 src/main/java/com/codesdream/ase/repository/auth/JSONTokenRepository.java create mode 100644 src/main/java/com/codesdream/ase/service/AuthService.java create mode 100644 src/main/java/com/codesdream/ase/service/IAuthService.java 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..9b5dced --- /dev/null +++ b/src/main/java/com/codesdream/ase/component/auth/JSONRandomCodeGenerator.java @@ -0,0 +1,18 @@ +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 apiSHA1){ + return encoder.encode(String.format("RandomCode [%s][%s][%s]", + username, date.toString(), apiSHA1)); + } +} 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..cafc05d --- /dev/null +++ b/src/main/java/com/codesdream/ase/component/auth/JSONTokenAuthenticationToken.java @@ -0,0 +1,58 @@ +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 { + // token 产生的签名 + String signed = null; + // 用户名 + Object principal = null; + // JSON 特征随机代码 + String randomCode = 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 signed, Collection authorities) { + super(authorities); + this.principal = principal; + this.randomCode = null; + this.signed = signed; + setAuthenticated(true); + } + + public JSONTokenAuthenticationToken(String principal, String randomCode, String signed) { + super(null); + this.principal = principal; + this.randomCode = randomCode; + this.signed = signed; + setAuthenticated(false); + } + + @Override + public String getCredentials() { + return signed; + } + + @Override + public Object getPrincipal() { + return principal; + } + + public String getRandomCode() { + return randomCode; + } + + public void setRandomCode(String randomCode) { + this.randomCode = randomCode; + } +} 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..726d4e4 100644 --- a/src/main/java/com/codesdream/ase/component/datamanager/JSONParameter.java +++ b/src/main/java/com/codesdream/ase/component/datamanager/JSONParameter.java @@ -19,7 +19,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; 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..4f82267 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,13 @@ import lombok.Data; @Data public class UserLoginChecker { + // 请求类型 private String checkType; private String username; private String password; + +// // 客户端类型 +// private String clientType; +// // JSON签名 +// private String signed; } 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..dd624d9 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,16 +1,15 @@ package com.codesdream.ase.component.json.respond; +import com.codesdream.ase.component.json.JSONBaseObject; import lombok.Data; import java.util.Date; -// 服务端返回的JSON对象基础信息 +// 服务端返回的JSON对象模板 @Data -public class JSONBaseRespondObject { +public class JSONBaseRespondObject extends JSONBaseObject { // 请求成功状态 String status = "fail"; - // 时间 - Date time = new Date(); public JSONBaseRespondObject(){ 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..e4112ca 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 @@ -10,7 +10,7 @@ public class UserLoginCheckerJSONRespond extends JSONBaseRespondObject { boolean loginStatus = false; boolean userBanned = false; String respondInformation = ""; - String sessionId = ""; + String token = ""; public UserLoginCheckerJSONRespond(){ super("success"); 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..48b087e --- /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 extends JSONBaseRespondObject { + String token; +} 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..86ab588 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); 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..d8ea606 100644 --- a/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationSuccessHandler.java +++ b/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationSuccessHandler.java @@ -3,12 +3,14 @@ package com.codesdream.ase.component.permission; 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,29 +28,36 @@ 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()); // 获得session id - WebAuthenticationDetails webAuthenticationDetails = (WebAuthenticationDetails) (authentication.getDetails()); - respond.setSessionId(webAuthenticationDetails.getSessionId()); + /*WebAuthenticationDetails webAuthenticationDetails = (WebAuthenticationDetails) (authentication.getDetails());*/ + User user = (User) authentication.getPrincipal(); + // 获得api token + Optional tokenOptional = authService.userNewTokenGetter(user.getUsername()); + if(tokenOptional.isPresent()){ + respond.setToken(tokenOptional.get()); + } + else respond.setToken(""); response.getWriter().write(jsonParameter.getJSONString(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..d22ec0d --- /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.alibaba.fastjson.JSONObject; +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.exception.JSONTokenExpiredException; +import com.codesdream.ase.exception.JSONTokenIncorrectSignedException; +import com.codesdream.ase.model.auth.JSONToken; +import com.codesdream.ase.service.AuthService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +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.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.stereotype.Component; +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.nio.file.attribute.UserPrincipalNotFoundException; +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 rawDate = request.getHeader("date"); + + // 对API的内容取哈希码 + String apiSHA1 = request.getHeader("apiSHA1"); + + if (signed != null && username != null && rawDate != null && apiSHA1 != null) { + // 获得具体时间 + Date date = new Date(Long.parseLong(rawDate)); + + // 检查时间是否合理 + // ... + + // 生成特征随机代码 + String randomCode = randomCodeGenerator.generateRandomCode(username, date, apiSHA1); + + // 进行验证 + Optional optionalJSONToken = authService.findTokenByUserName(username); + if(!optionalJSONToken.isPresent()){ + throw new UserPrincipalNotFoundException("Token Not Found"); + } + + // 检查token是否过期 + JSONToken token = optionalJSONToken.get(); + if(!authService.checkTokenIfExpired(token)) { + + log.info(String.format("Determined Signed: %s", + signedGenerator.generateSigned(username, randomCode, token.getToken()))); + + // 检查签名是否正确 + if (signed.equals(signedGenerator.generateSigned(username, randomCode, token.getToken()))) { + + // 查询用户的相关信息 + UserDetails user = userDetailsService.loadUserByUsername(username); + + Collection authorities = user.getAuthorities(); + + // 生成认证柄 (储存上下文信息) + JSONTokenAuthenticationToken authentication = new JSONTokenAuthenticationToken(user, signed, authorities); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + + } + + filterChain.doFilter(request, response); + + } + +} 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..9bf6a94 100644 --- a/src/main/java/com/codesdream/ase/component/permission/ASESecurityAuthenticationProvider.java +++ b/src/main/java/com/codesdream/ase/component/permission/ASESecurityAuthenticationProvider.java @@ -14,6 +14,7 @@ import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.Collection; +// 普通用户名密码验证, 用户获得Token @Slf4j @Component public class ASESecurityAuthenticationProvider implements AuthenticationProvider { @@ -63,6 +64,6 @@ public class ASESecurityAuthenticationProvider implements AuthenticationProvider @Override public boolean supports(Class aClass) { - return true; + return aClass.equals(UsernamePasswordAuthenticationToken.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..2868d42 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,6 @@ package com.codesdream.ase.component.permission; +import com.codesdream.ase.component.auth.AJAXRequestChecker; import com.codesdream.ase.component.datamanager.JSONParameter; import com.codesdream.ase.component.json.request.UserLoginChecker; import lombok.extern.slf4j.Slf4j; @@ -22,41 +23,45 @@ 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)) { + log.info("NOT AJAX POST 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"); + log.info("JSON Object 2 Java Object Success."); + 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(); + + 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); + } } 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..9796ea7 100644 --- a/src/main/java/com/codesdream/ase/controller/LoginController.java +++ b/src/main/java/com/codesdream/ase/controller/LoginController.java @@ -17,6 +17,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; +import java.security.Principal; import java.util.Optional; @@ -41,9 +42,9 @@ 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); @@ -51,10 +52,11 @@ public class LoginController { 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(); // 构造返回对象 @@ -68,4 +70,29 @@ public class LoginController { } } + // 根据学号计算对应的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 FailedSONRespond()); + + 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 JSONBaseRespondObject()); + } + + + } + + + } 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..e88c51b --- /dev/null +++ b/src/main/java/com/codesdream/ase/model/auth/JSONToken.java @@ -0,0 +1,28 @@ +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; + + // 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..f88aff7 --- /dev/null +++ b/src/main/java/com/codesdream/ase/service/AuthService.java @@ -0,0 +1,57 @@ +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) { + 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 + 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..a3f6a7b --- /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); +} 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())); From 80e46c8cb8a28b9d1ca68d3d92b1b7b144cbfa7c Mon Sep 17 00:00:00 2001 From: Saturneric Date: Mon, 16 Mar 2020 12:41:15 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=AE=A4=E8=AF=81?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E7=9A=84API=E6=8E=A5=E5=8F=A3=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F;=E5=8A=A0=E5=85=A5=E5=AE=A2=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E4=BB=A3=E5=8F=B7,=20=E5=8F=96=E6=B6=88dataSHA1;=E8=B0=83?= =?UTF-8?q?=E6=95=B4Spring=20Security;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/JSONRandomCodeGenerator.java | 5 +- .../auth/JSONTokenAuthenticationToken.java | 34 ++++--- ...enUsernamePasswordAuthenticationToken.java | 38 ++++++++ .../component/datamanager/JSONParameter.java | 36 ++++++++ .../json/request/UserLoginChecker.java | 5 +- .../json/respond/EmptyDataObjectRespond.java | 5 + .../json/respond/FailedSONRespond.java | 9 -- .../json/respond/JSONBaseRespondObject.java | 45 ++++++++- .../respond/JSONStandardFailedRespond.java | 8 ++ .../respond/UserLoginCheckerJSONRespond.java | 7 +- .../respond/UserLoginTokenJSONRespond.java | 2 +- .../ASEAuthenticationEntryPoint.java | 15 +-- .../ASEAuthenticationFailureHandler.java | 2 +- .../ASEAuthenticationSuccessHandler.java | 26 +++--- .../ASEJSONTokenAuthenticationFilter.java | 92 +++++++++---------- .../ASESecurityAuthenticationProvider.java | 17 +++- ...EUsernamePasswordAuthenticationFilter.java | 15 ++- .../ase/controller/LoginController.java | 11 +-- .../codesdream/ase/model/auth/JSONToken.java | 3 + .../codesdream/ase/service/AuthService.java | 8 +- .../codesdream/ase/service/IAuthService.java | 2 +- 21 files changed, 250 insertions(+), 135 deletions(-) create mode 100644 src/main/java/com/codesdream/ase/component/auth/JSONTokenUsernamePasswordAuthenticationToken.java create mode 100644 src/main/java/com/codesdream/ase/component/json/respond/EmptyDataObjectRespond.java delete mode 100644 src/main/java/com/codesdream/ase/component/json/respond/FailedSONRespond.java create mode 100644 src/main/java/com/codesdream/ase/component/json/respond/JSONStandardFailedRespond.java diff --git a/src/main/java/com/codesdream/ase/component/auth/JSONRandomCodeGenerator.java b/src/main/java/com/codesdream/ase/component/auth/JSONRandomCodeGenerator.java index 9b5dced..954850b 100644 --- a/src/main/java/com/codesdream/ase/component/auth/JSONRandomCodeGenerator.java +++ b/src/main/java/com/codesdream/ase/component/auth/JSONRandomCodeGenerator.java @@ -6,13 +6,14 @@ 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 apiSHA1){ + public String generateRandomCode(String username, Date date, String clientCode){ return encoder.encode(String.format("RandomCode [%s][%s][%s]", - username, date.toString(), apiSHA1)); + username, date.toString(), clientCode)); } } diff --git a/src/main/java/com/codesdream/ase/component/auth/JSONTokenAuthenticationToken.java b/src/main/java/com/codesdream/ase/component/auth/JSONTokenAuthenticationToken.java index cafc05d..92bf5f9 100644 --- a/src/main/java/com/codesdream/ase/component/auth/JSONTokenAuthenticationToken.java +++ b/src/main/java/com/codesdream/ase/component/auth/JSONTokenAuthenticationToken.java @@ -7,14 +7,15 @@ import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; -// 关联Token与其他用户的相关数据的认证对象 +// 关联Token与其他用户的相关数据的授权柄 public class JSONTokenAuthenticationToken extends AbstractAuthenticationToken { - // token 产生的签名 - String signed = null; + // 客户端签名 + private String signed = null; // 用户名 - Object principal = null; - // JSON 特征随机代码 - String randomCode = null; + private Object principal = null; + // 客户端代码 + private String clientCode = null; + /** * Creates a token with the supplied array of authorities. @@ -22,18 +23,21 @@ public class JSONTokenAuthenticationToken extends AbstractAuthenticationToken { * @param authorities the collection of GrantedAuthoritys for the principal * represented by this authentication object. */ - public JSONTokenAuthenticationToken(UserDetails principal, String signed, Collection authorities) { + public JSONTokenAuthenticationToken(UserDetails principal, + String clientCode, + Collection authorities) + { super(authorities); this.principal = principal; - this.randomCode = null; - this.signed = signed; + this.clientCode = clientCode; + this.signed = null; setAuthenticated(true); } - public JSONTokenAuthenticationToken(String principal, String randomCode, String signed) { + public JSONTokenAuthenticationToken(String principal, String clientCode, String signed) { super(null); this.principal = principal; - this.randomCode = randomCode; + this.clientCode = clientCode; this.signed = signed; setAuthenticated(false); } @@ -48,11 +52,11 @@ public class JSONTokenAuthenticationToken extends AbstractAuthenticationToken { return principal; } - public String getRandomCode() { - return randomCode; + public String getClientCode() { + return clientCode; } - public void setRandomCode(String randomCode) { - this.randomCode = randomCode; + 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/datamanager/JSONParameter.java b/src/main/java/com/codesdream/ase/component/datamanager/JSONParameter.java index 726d4e4..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; @@ -56,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); @@ -67,4 +101,6 @@ public class JSONParameter { return json.map(jsonObject -> getJavaObject(jsonObject, type)); } + + } 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 4f82267..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 @@ -8,9 +8,6 @@ public class UserLoginChecker { private String checkType; private String username; private String password; + private String clientCode; -// // 客户端类型 -// private String clientType; -// // JSON签名 -// private String signed; } 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 dd624d9..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 @@ -2,20 +2,57 @@ 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对象模板 +@EqualsAndHashCode(callSuper = true) @Data public class JSONBaseRespondObject extends JSONBaseObject { - // 请求成功状态 - String status = "fail"; - 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 e4112ca..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 token = ""; - public UserLoginCheckerJSONRespond(){ - super("success"); - } - } 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 index 48b087e..43272ab 100644 --- a/src/main/java/com/codesdream/ase/component/json/respond/UserLoginTokenJSONRespond.java +++ b/src/main/java/com/codesdream/ase/component/json/respond/UserLoginTokenJSONRespond.java @@ -1,6 +1,6 @@ package com.codesdream.ase.component.json.respond; // 返回登录后的授权码(用于客户端生成签名) -public class UserLoginTokenJSONRespond extends JSONBaseRespondObject { +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 86ab588..1680ec3 100644 --- a/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationFailureHandler.java +++ b/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationFailureHandler.java @@ -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 d8ea606..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,5 +1,6 @@ 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; @@ -35,31 +36,26 @@ public class ASEAuthenticationSuccessHandler extends SavedRequestAwareAuthentica 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); - } -*/ UserLoginCheckerJSONRespond respond = new UserLoginCheckerJSONRespond(); respond.setUserExist(authentication.isAuthenticated()); respond.setLoginStatus(authentication.isAuthenticated()); + respond.setRespondInformation("Authentication Success"); + + // 获得 JSONTokenAuthenticationToken + JSONTokenAuthenticationToken authenticationToken = (JSONTokenAuthenticationToken) authentication; + + User user = (User) authenticationToken.getPrincipal(); + + Optional tokenOptional = authService.userNewTokenGetter( + user.getUsername(), authenticationToken.getClientCode()); - // 获得session id - /*WebAuthenticationDetails webAuthenticationDetails = (WebAuthenticationDetails) (authentication.getDetails());*/ - User user = (User) authentication.getPrincipal(); - // 获得api token - Optional tokenOptional = authService.userNewTokenGetter(user.getUsername()); if(tokenOptional.isPresent()){ respond.setToken(tokenOptional.get()); } else respond.setToken(""); - response.getWriter().write(jsonParameter.getJSONString(respond)); + 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 index d22ec0d..a518944 100644 --- a/src/main/java/com/codesdream/ase/component/permission/ASEJSONTokenAuthenticationFilter.java +++ b/src/main/java/com/codesdream/ase/component/permission/ASEJSONTokenAuthenticationFilter.java @@ -1,29 +1,18 @@ package com.codesdream.ase.component.permission; -import com.alibaba.fastjson.JSONObject; 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.exception.JSONTokenExpiredException; -import com.codesdream.ase.exception.JSONTokenIncorrectSignedException; import com.codesdream.ase.model.auth.JSONToken; import com.codesdream.ase.service.AuthService; import lombok.extern.slf4j.Slf4j; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; 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.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.annotation.Resource; @@ -32,7 +21,6 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.nio.file.attribute.UserPrincipalNotFoundException; import java.util.Collection; import java.util.Date; import java.util.Optional; @@ -64,56 +52,68 @@ public class ASEJSONTokenAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + // 用户名 String username = request.getHeader( "username"); + // 客户端签名 String signed = request.getHeader("signed"); - String rawDate = request.getHeader("date"); + // 时间戳 + String timestamp = request.getHeader("timestamp"); - // 对API的内容取哈希码 - String apiSHA1 = request.getHeader("apiSHA1"); - - if (signed != null && username != null && rawDate != null && apiSHA1 != null) { + if (signed != null && username != null && timestamp != null) { // 获得具体时间 - Date date = new Date(Long.parseLong(rawDate)); + Date date = new Date(Long.parseLong(timestamp)); - // 检查时间是否合理 - // ... + Date now = new Date(); - // 生成特征随机代码 - String randomCode = randomCodeGenerator.generateRandomCode(username, date, apiSHA1); + // 限制时间戳有效区间为60s + long dtTime = 60*1000; + Date maxDate = new Date(now.getTime() + dtTime); - // 进行验证 - Optional optionalJSONToken = authService.findTokenByUserName(username); - if(!optionalJSONToken.isPresent()){ - throw new UserPrincipalNotFoundException("Token Not Found"); - } + // 检查时间戳是否合理 + if(maxDate.after(date)) { + // 从服务器中查找token + Optional optionalJSONToken = authService.findTokenByUserName(username); + if (optionalJSONToken.isPresent()) { + JSONToken token = optionalJSONToken.get(); - // 检查token是否过期 - JSONToken token = optionalJSONToken.get(); - if(!authService.checkTokenIfExpired(token)) { + // 检查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()))); + log.info(String.format("Determined Signed: %s", + signedGenerator.generateSigned(username, randomCode, token.getToken()))); - // 检查签名是否正确 - if (signed.equals(signedGenerator.generateSigned(username, randomCode, token.getToken()))) { - - // 查询用户的相关信息 - UserDetails user = userDetailsService.loadUserByUsername(username); - - Collection authorities = user.getAuthorities(); - - // 生成认证柄 (储存上下文信息) - JSONTokenAuthenticationToken authentication = new JSONTokenAuthenticationToken(user, signed, authorities); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - - SecurityContextHolder.getContext().setAuthentication(authentication); + // 检查签名是否正确 + 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 9bf6a94..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; @@ -29,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); @@ -58,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 aClass.equals(UsernamePasswordAuthenticationToken.class); + 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 2868d42..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,6 +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; @@ -10,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; @@ -32,19 +34,19 @@ public class ASEUsernamePasswordAuthenticationFilter extends UsernamePasswordAut // 判断是否为AJAX请求格式的数据 if(!ajaxRequestChecker.checkAjaxPOSTRequest(request)) { - log.info("NOT AJAX POST 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"); - log.info("JSON Object 2 Java Object Success."); + 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 = ""; @@ -53,15 +55,12 @@ public class ASEUsernamePasswordAuthenticationFilter extends UsernamePasswordAut username = username.trim(); password = password.trim(); - UsernamePasswordAuthenticationToken authRequest = - new UsernamePasswordAuthenticationToken(username, password); + JSONTokenUsernamePasswordAuthenticationToken authRequest = + new JSONTokenUsernamePasswordAuthenticationToken(username, password, clientCode); - log.info(String.format("User AJAX JSON Authentication: %s %s.", username, password)); - - setDetails(request, authRequest); + authRequest.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); return this.getAuthenticationManager().authenticate(authRequest); - } } diff --git a/src/main/java/com/codesdream/ase/controller/LoginController.java b/src/main/java/com/codesdream/ase/controller/LoginController.java index 9796ea7..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; @@ -17,7 +17,6 @@ import org.springframework.web.bind.annotation.ResponseBody; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; -import java.security.Principal; import java.util.Optional; @@ -48,7 +47,7 @@ public class LoginController { // 检查是否为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); @@ -66,7 +65,7 @@ public class LoginController { } else { // 返回失败对象 - return jsonParameter.getJSONString(new JSONBaseRespondObject()); + return jsonParameter.getJSONString(new JSONStandardFailedRespond()); } } @@ -76,7 +75,7 @@ public class LoginController { String checkUsernameByStudentID(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); @@ -87,7 +86,7 @@ public class LoginController { } else { // 返回失败对象 - return jsonParameter.getJSONString(new JSONBaseRespondObject()); + return jsonParameter.getJSONString(new JSONStandardFailedRespond()); } diff --git a/src/main/java/com/codesdream/ase/model/auth/JSONToken.java b/src/main/java/com/codesdream/ase/model/auth/JSONToken.java index e88c51b..751ebf1 100644 --- a/src/main/java/com/codesdream/ase/model/auth/JSONToken.java +++ b/src/main/java/com/codesdream/ase/model/auth/JSONToken.java @@ -23,6 +23,9 @@ public class JSONToken { @Column(unique = true) private String token; + // 客户端标识口令 + private String clientCode; + // token过期时间 private Date expiredDate; } diff --git a/src/main/java/com/codesdream/ase/service/AuthService.java b/src/main/java/com/codesdream/ase/service/AuthService.java index f88aff7..1784365 100644 --- a/src/main/java/com/codesdream/ase/service/AuthService.java +++ b/src/main/java/com/codesdream/ase/service/AuthService.java @@ -36,18 +36,24 @@ public class AuthService implements IAuthService { } @Override - public Optional userNewTokenGetter(String username) { + 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()); diff --git a/src/main/java/com/codesdream/ase/service/IAuthService.java b/src/main/java/com/codesdream/ase/service/IAuthService.java index a3f6a7b..fc4eae0 100644 --- a/src/main/java/com/codesdream/ase/service/IAuthService.java +++ b/src/main/java/com/codesdream/ase/service/IAuthService.java @@ -12,5 +12,5 @@ public interface IAuthService { boolean checkTokenIfExpired(JSONToken token); // 为用户获得一个新的API Token - Optional userNewTokenGetter(String username); + Optional userNewTokenGetter(String username, String clientCode); } From 9515ebbc39e2466262387d25c55f84c412e15e2e Mon Sep 17 00:00:00 2001 From: Saturneric Date: Mon, 16 Mar 2020 15:25:34 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E5=AE=8C=E5=96=84=E8=AE=A4=E8=AF=81?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=AD=90=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/JSONRandomCodeGenerator.java | 2 +- .../auth/TimestampExpiredChecker.java | 18 ++++++++++++++++++ .../component/datamanager/JSONParameter.java | 8 +++++++- .../respond/UserLoginCheckerJSONRespond.java | 9 +++++---- .../permission/ASEAccessDeniedHandler.java | 10 ++-------- .../ASEAuthenticationEntryPoint.java | 4 ++-- .../ASEAuthenticationFailureHandler.java | 7 ++++--- ...SEUsernamePasswordAuthenticationFilter.java | 12 ++++++++++++ 8 files changed, 51 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/codesdream/ase/component/auth/TimestampExpiredChecker.java diff --git a/src/main/java/com/codesdream/ase/component/auth/JSONRandomCodeGenerator.java b/src/main/java/com/codesdream/ase/component/auth/JSONRandomCodeGenerator.java index 954850b..fe2ce6c 100644 --- a/src/main/java/com/codesdream/ase/component/auth/JSONRandomCodeGenerator.java +++ b/src/main/java/com/codesdream/ase/component/auth/JSONRandomCodeGenerator.java @@ -14,6 +14,6 @@ public class JSONRandomCodeGenerator { public String generateRandomCode(String username, Date date, String clientCode){ return encoder.encode(String.format("RandomCode [%s][%s][%s]", - username, date.toString(), clientCode)); + username, Long.toString(date.getTime()), clientCode)); } } diff --git a/src/main/java/com/codesdream/ase/component/auth/TimestampExpiredChecker.java b/src/main/java/com/codesdream/ase/component/auth/TimestampExpiredChecker.java new file mode 100644 index 0000000..080bd20 --- /dev/null +++ b/src/main/java/com/codesdream/ase/component/auth/TimestampExpiredChecker.java @@ -0,0 +1,18 @@ +package com.codesdream.ase.component.auth; + +import org.springframework.stereotype.Component; + +import java.util.Date; + +// 验证时间戳是否有效 +@Component +public class TimestampExpiredChecker { + + public boolean checkTimestampBeforeMaxTime(String timestamp, int seconds){ + Date timestampDate = new Date(Long.parseLong(timestamp)); + long currentTime = System.currentTimeMillis(); + Date maxDate = new Date(currentTime + seconds * 1000); + return timestampDate.before(maxDate); + } + +} 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 103c5a0..3fd6706 100644 --- a/src/main/java/com/codesdream/ase/component/datamanager/JSONParameter.java +++ b/src/main/java/com/codesdream/ase/component/datamanager/JSONParameter.java @@ -86,7 +86,13 @@ public class JSONParameter { // 获得标准的JSON响应字符串返回(403状态) public String getJSONStandardRespond403(){ - JSONBaseRespondObject respondObject = new JSONBaseRespondObject(403, "forbidden"); + JSONBaseRespondObject respondObject = new JSONBaseRespondObject(403, "Forbidden"); + return getJSONString(respondObject); + } + + // 获得标准的JSON响应字符串返回(401状态) + public String getJSONStandardRespond401(){ + JSONBaseRespondObject respondObject = new JSONBaseRespondObject(401, "Unauthorized"); return getJSONString(respondObject); } 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 79ccfe9..208851a 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 @@ -1,14 +1,15 @@ package com.codesdream.ase.component.json.respond; +import com.sun.org.apache.xpath.internal.operations.Bool; import lombok.Data; import lombok.EqualsAndHashCode; @Data public class UserLoginCheckerJSONRespond { - boolean userExist = false; - boolean loginStatus = false; - boolean userBanned = false; + Boolean userExist = null; + Boolean userBanned = null; + Boolean loginStatus = null; String respondInformation = ""; - String token = ""; + String token = null; } diff --git a/src/main/java/com/codesdream/ase/component/permission/ASEAccessDeniedHandler.java b/src/main/java/com/codesdream/ase/component/permission/ASEAccessDeniedHandler.java index 2043f27..67955f5 100644 --- a/src/main/java/com/codesdream/ase/component/permission/ASEAccessDeniedHandler.java +++ b/src/main/java/com/codesdream/ase/component/permission/ASEAccessDeniedHandler.java @@ -26,15 +26,9 @@ public class ASEAccessDeniedHandler implements AccessDeniedHandler { throws IOException, ServletException { log.info("ASEAccessDeniedHandler Found!"); - response.setCharacterEncoding("utf-8"); - response.setContentType("text/javascript;charset=utf-8"); - UserLoginCheckerJSONRespond checkerRespond = new UserLoginCheckerJSONRespond(); - checkerRespond.setLoginStatus(true); - checkerRespond.setUserExist(true); - checkerRespond.setRespondInformation("Authenticated user has no access to this resource"); + // 对无权限操作返回403 + response.getWriter().print(jsonParameter.getJSONStandardRespond403()); - // 对匿名用户返回 - response.getWriter().print(jsonParameter.getJSONString(checkerRespond)); } } 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 3e62a3f..b367794 100644 --- a/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationEntryPoint.java +++ b/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationEntryPoint.java @@ -24,8 +24,8 @@ public class ASEAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { - // 对匿名用户返回403 - response.getWriter().print(jsonParameter.getJSONStandardRespond403()); + // 对匿名用户返回401 + response.getWriter().print(jsonParameter.getJSONStandardRespond401()); } } 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 1680ec3..393d591 100644 --- a/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationFailureHandler.java +++ b/src/main/java/com/codesdream/ase/component/permission/ASEAuthenticationFailureHandler.java @@ -23,13 +23,14 @@ public class ASEAuthenticationFailureHandler extends SimpleUrlAuthenticationFail @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) - throws IOException, ServletException + throws IOException { log.info("ASEAuthenticationFailureHandler Login Fail!"); UserLoginCheckerJSONRespond respond = new UserLoginCheckerJSONRespond(); - respond.setUserExist(false); + + respond.setUserExist(null); + respond.setUserBanned(null); respond.setLoginStatus(false); - respond.setUserBanned(true); respond.setRespondInformation("Authentication Failed"); // 填充response对象 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 2be84dd..7c78ae6 100644 --- a/src/main/java/com/codesdream/ase/component/permission/ASEUsernamePasswordAuthenticationFilter.java +++ b/src/main/java/com/codesdream/ase/component/permission/ASEUsernamePasswordAuthenticationFilter.java @@ -2,6 +2,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.auth.TimestampExpiredChecker; import com.codesdream.ase.component.datamanager.JSONParameter; import com.codesdream.ase.component.json.request.UserLoginChecker; import lombok.extern.slf4j.Slf4j; @@ -12,6 +13,7 @@ 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 org.springframework.web.bind.annotation.RequestMapping; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; @@ -28,10 +30,20 @@ public class ASEUsernamePasswordAuthenticationFilter extends UsernamePasswordAut @Resource private AJAXRequestChecker ajaxRequestChecker; + @Resource + private TimestampExpiredChecker timestampExpiredChecker; + @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { + String timestamp = request.getHeader("timestamp"); + + // 检查时间戳是否合理(60秒内) + if(!timestampExpiredChecker.checkTimestampBeforeMaxTime(timestamp, 60)){ + throw new AuthenticationServiceException("Timestamp Expired."); + } + // 判断是否为AJAX请求格式的数据 if(!ajaxRequestChecker.checkAjaxPOSTRequest(request)) { throw new AuthenticationServiceException("Authentication method not supported: NOT Ajax Method.");