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()));