pom.xml
@@ -216,8 +216,6 @@ <version>4.10.0</version> </dependency> <!-- okhttp-digest --> <dependency> <groupId>io.github.rburgst</groupId> @@ -226,10 +224,17 @@ </dependency> <!-- https://mvnrepository.com/artifact/net.sf.kxml/kxml2 --> <!-- <dependency>--> <!-- <groupId>net.sf.kxml</groupId>--> <!-- <artifactId>kxml2</artifactId>--> <!-- <version>2.3.0</version>--> <!-- </dependency>--> <!-- jwt实现 --> <dependency> <groupId>net.sf.kxml</groupId> <artifactId>kxml2</artifactId> <version>2.3.0</version> <groupId>org.bitbucket.b_c</groupId> <artifactId>jose4j</artifactId> <version>0.9.3</version> </dependency> <!--反向代理--> src/main/java/com/genersoft/iot/vmp/conf/security/AnonymousAuthenticationEntryPoint.java
@@ -1,10 +1,11 @@ package com.genersoft.iot.vmp.conf.security; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.conf.security.dto.JwtUser; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; @@ -17,12 +18,17 @@ * @author lin */ @Component public class AnonymousAuthenticationEntryPoint implements AuthenticationEntryPoint { private final static Logger logger = LoggerFactory.getLogger(DefaultUserDetailsServiceImpl.class); public class AnonymousAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) { System.err.println(e.getMessage()); String jwt = request.getHeader(JwtUtils.getHeader()); JwtUser jwtUser = JwtUtils.verifyToken(jwt); String username = jwtUser.getUserName(); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, jwtUser.getPassword() ); SecurityContextHolder.getContext().setAuthentication(token); System.out.println(jwt); // 允许跨域 String origin = request.getHeader("Origin"); response.setHeader("Access-Control-Allow-Credentials", "true"); src/main/java/com/genersoft/iot/vmp/conf/security/DefaultUserDetailsServiceImpl.java
@@ -1,7 +1,9 @@ package com.genersoft.iot.vmp.conf.security; import java.time.LocalDateTime; import com.alibaba.excel.util.StringUtils; import com.genersoft.iot.vmp.conf.security.dto.LoginUser; import com.genersoft.iot.vmp.service.IUserService; import com.genersoft.iot.vmp.storager.dao.dto.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -10,10 +12,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; import com.alibaba.excel.util.StringUtils; import com.genersoft.iot.vmp.conf.security.dto.LoginUser; import com.genersoft.iot.vmp.service.IUserService; import com.genersoft.iot.vmp.storager.dao.dto.User; import java.time.LocalDateTime; /** * 用户登录认证逻辑 @@ -45,4 +44,8 @@ } } src/main/java/com/genersoft/iot/vmp/conf/security/InvalidSessionHandler.java
File was deleted src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java
New file @@ -0,0 +1,65 @@ package com.genersoft.iot.vmp.conf.security; import com.genersoft.iot.vmp.conf.security.dto.JwtUser; import org.apache.commons.lang3.StringUtils; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; /** * jwt token 过滤器 */ @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String jwt = request.getHeader(JwtUtils.getHeader()); // 这里如果没有jwt,继续往后走,因为后面还有鉴权管理器等去判断是否拥有身份凭证,所以是可以放行的 // 没有jwt相当于匿名访问,若有一些接口是需要权限的,则不能访问这些接口 if (StringUtils.isBlank(jwt)) { chain.doFilter(request, response); return; } JwtUser jwtUser = JwtUtils.verifyToken(jwt); String username = jwtUser.getUserName(); // TODO 处理各个状态 switch (jwtUser.getStatus()){ case EXPIRED: response.setStatus(400); chain.doFilter(request, response); // 异常 return; case EXCEPTION: // 过期 response.setStatus(400); chain.doFilter(request, response); return; case EXPIRING_SOON: // 即将过期 // return; default: } // String password = SecurityUtils.encryptPassword(jwtUser.getPassword()); // user.setPassword(password); // 构建UsernamePasswordAuthenticationToken,这里密码为null,是因为提供了正确的JWT,实现自动登录 UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, jwtUser.getPassword(), new ArrayList<>() ); SecurityContextHolder.getContext().setAuthentication(token); chain.doFilter(request, response); } } src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java
New file @@ -0,0 +1,138 @@ package com.genersoft.iot.vmp.conf.security; import com.genersoft.iot.vmp.conf.security.dto.JwtUser; import org.jose4j.json.JsonUtil; import org.jose4j.jwk.RsaJsonWebKey; import org.jose4j.jws.AlgorithmIdentifiers; import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.NumericDate; import org.jose4j.jwt.consumer.ErrorCodes; import org.jose4j.jwt.consumer.InvalidJwtException; import org.jose4j.jwt.consumer.JwtConsumer; import org.jose4j.jwt.consumer.JwtConsumerBuilder; import org.jose4j.lang.JoseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.security.PrivateKey; import java.time.LocalDateTime; import java.time.ZoneOffset; public class JwtUtils { private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class); private static final String HEADER = "Access-Token"; private static final String AUDIENCE = "Audience"; private static final long EXPIRED_THRESHOLD = 10 * 60; private static final String keyId = "3e79646c4dbc408383a9eed09f2b85ae"; private static final String privateKeyStr = "{\"kty\":\"RSA\",\"kid\":\"3e79646c4dbc408383a9eed09f2b85ae\",\"alg\":\"RS256\",\"n\":\"gndmVdiOTSJ5et2HIeTM5f1m61x5ojLUi5HDfvr-jRrESQ5kbKuySGHVwR4QhwinpY1wQqBnwc80tx7cb_6SSqsTOoGln6T_l3k2Pb54ClVnGWiW_u1kmX78V2TZOsVmZmwtdZCMi-2zWIyAdIEXE-gncIehoAgEoq2VAhaCURbJWro_EwzzQwNmCTkDodLAx4npXRd_qSu0Ayp0txym9OFovBXBULRvk4DPiy3i_bPUmCDxzC46pTtFOe9p82uybTehZfULZtXXqRm85FL9n5zkrsTllPNAyEGhgb0RK9sE5nK1m_wNNysDyfLC4EFf1VXTrKm14XNVjc2vqLb7Mw\",\"e\":\"AQAB\",\"d\":\"ed7U_k3rJ4yTk70JtRSIfjKGiEb67BO1TabcymnljKO7RU8nage84zZYuSu_XpQsHk6P1f0Gzxkicghm_Er-FrfVn2pp70Xu52z3yRd6BJUgWLDFk97ngScIyw5OiULKU9SrZk2frDpftNCSUcIgb50F8m0QAnBa_CdPsQKbuuhLv8V8tBAV7F_lAwvSBgu56wRo3hPz5dWH8YeXM7XBfQ9viFMNEKd21sP_j5C7ueUnXT66nBxe3ZJEU3iuMYM6D6dB_KW2GfZC6WmTgvGhhxJD0h7aYmfjkD99MDleB7SkpbvoODOqiQ5Epb7Nyh6kv5u4KUv2CJYtATLZkUeMkQ\",\"p\":\"uBUjWPWtlGksmOqsqCNWksfqJvMcnP_8TDYN7e4-WnHL4N-9HjRuPDnp6kHvCIEi9SEfxm7gNxlRcWegvNQr3IZCz7TnCTexXc5NOklB9OavWFla6u-s3Thn6Tz45-EUjpJr0VJMxhO-KxGmuTwUXBBp4vN6K2qV6rQNFmgkWzk\",\"q\":\"tW_i7cCec56bHkhITL_79dXHz_PLC_f7xlynmlZJGU_d6mqOKmLBNBbTMLnYW8uAFiFzWxDeDHh1o5uF0mSQR-Z1Fg35OftnpbWpy0Cbc2la5WgXQjOwtG1eLYIY2BD3-wQ1VYDBCvowr4FDi-sngxwLqvwmrJ0xjhi99O-Gzcs\",\"dp\":\"q1d5jE85Hz_6M-eTh_lEluEf0NtPEc-vvhw-QO4V-cecNpbrCBdTWBmr4dE3NdpFeJc5ZVFEv-SACyei1MBEh0ItI_pFZi4BmMfy2ELh8ptaMMkTOESYyVy8U7veDq9RnBcr5i1Nqr0rsBkA77-9T6gzdvycBZdzLYAkAmwzEvk\",\"dq\":\"q29A2K08Crs-jmp2Bi8Q_8QzvIX6wSBbwZ4ir24AO-5_HNP56IrPS0yV2GCB0pqCOGb6_Hz_koDvhtuYoqdqvMVAtMoXR3YJBUaVXPt65p4RyNmFwIPe31zHs_BNUTsXVRMw4c16mci03-Af1sEm4HdLfxAp6sfM3xr5wcnhcek\",\"qi\":\"rHPgVTyHUHuYzcxfouyBfb1XAY8nshwn0ddo81o1BccD4Z7zo5It6SefDHjxCAbcmbiCcXBSooLcY-NF5FMv3fg19UE21VyLQltHcVjRRp2tRs4OHcM8yaXIU2x6N6Z6BP2tOksHb9MOBY1wAQzFOAKg_G4Sxev6-_6ud6RISuc\"}"; private static final String publicKeyStr = "{\"kty\":\"RSA\",\"kid\":\"3e79646c4dbc408383a9eed09f2b85ae\",\"alg\":\"RS256\",\"n\":\"gndmVdiOTSJ5et2HIeTM5f1m61x5ojLUi5HDfvr-jRrESQ5kbKuySGHVwR4QhwinpY1wQqBnwc80tx7cb_6SSqsTOoGln6T_l3k2Pb54ClVnGWiW_u1kmX78V2TZOsVmZmwtdZCMi-2zWIyAdIEXE-gncIehoAgEoq2VAhaCURbJWro_EwzzQwNmCTkDodLAx4npXRd_qSu0Ayp0txym9OFovBXBULRvk4DPiy3i_bPUmCDxzC46pTtFOe9p82uybTehZfULZtXXqRm85FL9n5zkrsTllPNAyEGhgb0RK9sE5nK1m_wNNysDyfLC4EFf1VXTrKm14XNVjc2vqLb7Mw\",\"e\":\"AQAB\"}"; /** * token过期时间(分钟) */ public static final long expirationTime = 30; public static String createToken(String username, String password) { try { /** * “iss” (issuer) 发行人 * * “sub” (subject) 主题 * * “aud” (audience) 接收方 用户 * * “exp” (expiration time) 到期时间 * * “nbf” (not before) 在此之前不可用 * * “iat” (issued at) jwt的签发时间 */ //Payload JwtClaims claims = new JwtClaims(); claims.setGeneratedJwtId(); claims.setIssuedAtToNow(); // 令牌将过期的时间 分钟 claims.setExpirationTimeMinutesInTheFuture(expirationTime); claims.setNotBeforeMinutesInThePast(0); claims.setSubject("login"); claims.setAudience(AUDIENCE); //添加自定义参数,必须是字符串类型 claims.setClaim("username", username); claims.setClaim("password", password); //jws JsonWebSignature jws = new JsonWebSignature(); //签名算法RS256 jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); jws.setKeyIdHeaderValue(keyId); jws.setPayload(claims.toJson()); PrivateKey privateKey = new RsaJsonWebKey(JsonUtil.parseJson(privateKeyStr)).getPrivateKey(); jws.setKey(privateKey); //get token String idToken = jws.getCompactSerialization(); return idToken; } catch (JoseException e) { logger.error("[Token生成失败]: {}", e.getMessage()); } return null; } public static String getHeader() { return HEADER; } public static JwtUser verifyToken(String token) { JwtUser jwtUser = new JwtUser(); try { JwtConsumer consumer = new JwtConsumerBuilder() .setRequireExpirationTime() .setMaxFutureValidityInMinutes(5256000) .setAllowedClockSkewInSeconds(30) .setRequireSubject() //.setExpectedIssuer("") .setExpectedAudience(AUDIENCE) .setVerificationKey(new RsaJsonWebKey(JsonUtil.parseJson(publicKeyStr)).getPublicKey()) .build(); JwtClaims claims = consumer.processToClaims(token); NumericDate expirationTime = claims.getExpirationTime(); // 判断是否即将过期, 默认剩余时间小于5分钟未即将过期 // 剩余时间 (秒) long timeRemaining = LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8)) - expirationTime.getValue(); if (timeRemaining < 5 * 60) { jwtUser.setStatus(JwtUser.TokenStatus.EXPIRING_SOON); }else { jwtUser.setStatus(JwtUser.TokenStatus.NORMAL); } String username = (String) claims.getClaimValue("username"); String password = (String) claims.getClaimValue("password"); jwtUser.setUserName(username); jwtUser.setPassword(password); return jwtUser; } catch (InvalidJwtException e) { if (e.hasErrorCode(ErrorCodes.EXPIRED)) { jwtUser.setStatus(JwtUser.TokenStatus.EXPIRED); }else { jwtUser.setStatus(JwtUser.TokenStatus.EXCEPTION); } return jwtUser; }catch (Exception e) { logger.error("[Token解析失败]: {}", e.getMessage()); jwtUser.setStatus(JwtUser.TokenStatus.EXPIRED); return jwtUser; } } } src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java
@@ -21,7 +21,16 @@ @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { String username = request.getParameter("username"); logger.info("[登录成功] - [{}]", username); // String username = request.getParameter("username"); // httpServletResponse.setContentType("application/json;charset=UTF-8"); // // 生成JWT,并放置到请求头中 // String jwt = JwtUtils.createToken(authentication.getName(), ); // httpServletResponse.setHeader(JwtUtils.getHeader(), jwt); // ServletOutputStream outputStream = httpServletResponse.getOutputStream(); // outputStream.write(JSON.toJSONString(ErrorCode.SUCCESS).getBytes(StandardCharsets.UTF_8)); // outputStream.flush(); // outputStream.close(); // logger.info("[登录成功] - [{}]", username); } } src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java
@@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.conf.security; import com.genersoft.iot.vmp.conf.security.dto.LoginUser; import com.genersoft.iot.vmp.storager.dao.dto.User; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -9,6 +10,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import javax.security.sasl.AuthenticationException; import java.time.LocalDateTime; public class SecurityUtils { @@ -25,10 +27,16 @@ public static LoginUser login(String username, String password, AuthenticationManager authenticationManager) throws AuthenticationException { //使用security框架自带的验证token生成器 也可以自定义。 UsernamePasswordAuthenticationToken token =new UsernamePasswordAuthenticationToken(username,password); Authentication authenticate = authenticationManager.authenticate(token); SecurityContextHolder.getContext().setAuthentication(authenticate); LoginUser user = (LoginUser) authenticate.getPrincipal(); return user; // Authentication authenticate = authenticationManager.authenticate(token); // SecurityContextHolder.getContext().setAuthentication(authenticate); SecurityContextHolder.getContext().setAuthentication(token); // LoginUser user = (LoginUser) authenticate.getPrincipal(); User user = new User(); user.setUsername(username); LoginUser loginUser = new LoginUser(user, LocalDateTime.now()); return loginUser; } /** @@ -49,8 +57,13 @@ if(authentication!=null){ Object principal = authentication.getPrincipal(); if(principal!=null && !"anonymousUser".equals(principal)){ LoginUser user = (LoginUser) authentication.getPrincipal(); return user; // LoginUser user = (LoginUser) authentication.getPrincipal(); String username = (String) principal; User user = new User(); user.setUsername(username); LoginUser loginUser = new LoginUser(user, LocalDateTime.now()); return loginUser; } } return null; src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java
@@ -15,7 +15,9 @@ import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import java.util.List; @@ -56,22 +58,14 @@ */ @Autowired private AnonymousAuthenticationEntryPoint anonymousAuthenticationEntryPoint; // /** // * 超时处理 // */ // @Autowired // private InvalidSessionHandler invalidSessionHandler; @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; // /** // * 顶号处理 // */ // @Autowired // private SessionInformationExpiredHandler sessionInformationExpiredHandler; // /** // * 登录用户没有权限访问资源 // */ // @Autowired // private LoginUserAccessDeniedHandler accessDeniedHandler; // @Bean // JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception { // JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager()); // return jwtAuthenticationFilter; // } /** @@ -126,35 +120,56 @@ @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().csrf().disable(); // 设置允许添加静态文件 http.headers().contentTypeOptions().disable(); http.authorizeRequests() // 放行接口 http.headers().contentTypeOptions().disable() .and().cors() .and().csrf().disable() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 配置拦截规则 .and() .authorizeRequests() .antMatchers("/api/user/login","/index/hook/**").permitAll() // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated() // 异常处理(权限拒绝、登录失效等) .and().exceptionHandling() //匿名用户访问无权限资源时的异常处理 // 异常处理器 .and() .exceptionHandling() .authenticationEntryPoint(anonymousAuthenticationEntryPoint) // .accessDeniedHandler(accessDeniedHandler)//登录用户没有权限访问资源 // 登入 允许所有用户 .and().formLogin().permitAll() //登录成功处理逻辑 .successHandler(loginSuccessHandler) //登录失败处理逻辑 .failureHandler(loginFailureHandler) // 登出 .and().logout().logoutUrl("/api/user/logout").permitAll() //登出成功处理逻辑 .logoutSuccessHandler(logoutHandler) .deleteCookies("JSESSIONID") // 会话管理 // .and().sessionManagement().invalidSessionStrategy(invalidSessionHandler) // 超时处理 // .maximumSessions(1)//同一账号同时登录最大用户数 // .expiredSessionStrategy(sessionInformationExpiredHandler) // 顶号处理 // .accessDeniedHandler(jwtAccessDeniedHandler) // 配置自定义的过滤器 // .and() // .addFilter(jwtAuthenticationFilter) // 验证码过滤器放在UsernamePassword过滤器之前 // .addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class) ; http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // // 设置允许添加静态文件 // http.headers().contentTypeOptions().disable(); // http.authorizeRequests() // // 放行接口 // .antMatchers("/api/user/login","/index/hook/**").permitAll() // // 除上面外的所有请求全部需要鉴权认证 // .anyRequest().authenticated() // // 禁用session // .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // // 异常处理(权限拒绝、登录失效等) // .and().exceptionHandling() // // 匿名用户访问无权限资源时的异常处理 // .authenticationEntryPoint(anonymousAuthenticationEntryPoint) // // 登录 允许所有用户 // .and().formLogin() // // 登录成功处理逻辑 在这里给出JWT // .successHandler(loginSuccessHandler) // // 登录失败处理逻辑 // .failureHandler(loginFailureHandler) // // 登出 // .and().logout().logoutUrl("/api/user/logout").permitAll() // // 登出成功处理逻辑 // .logoutSuccessHandler(logoutHandler) // // 配置自定义的过滤器 // .and() // .addFilter(jwtAuthenticationFilter()) // ; } src/main/java/com/genersoft/iot/vmp/conf/security/dto/JwtUser.java
New file @@ -0,0 +1,53 @@ package com.genersoft.iot.vmp.conf.security.dto; public class JwtUser { public enum TokenStatus{ /** * 正常的使用状态 */ NORMAL, /** * 过期而失效 */ EXPIRED, /** * 即将过期 */ EXPIRING_SOON, /** * 异常 */ EXCEPTION } private String userName; private String password; private TokenStatus status; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public TokenStatus getStatus() { return status; } public void setStatus(TokenStatus status) { this.status = status; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java
@@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.vmanager.user; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.conf.security.SecurityUtils; import com.genersoft.iot.vmp.conf.security.dto.LoginUser; import com.genersoft.iot.vmp.service.IRoleService; @@ -21,6 +22,8 @@ import org.springframework.web.bind.annotation.*; import javax.security.sasl.AuthenticationException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.List; @Tag(name = "用户管理") @@ -43,7 +46,7 @@ @Operation(summary = "登录") @Parameter(name = "username", description = "用户名", required = true) @Parameter(name = "password", description = "密码(32位md5加密)", required = true) public LoginUser login(@RequestParam String username, @RequestParam String password){ public LoginUser login(HttpServletRequest request, HttpServletResponse response, @RequestParam String username, @RequestParam String password){ LoginUser user = null; try { user = SecurityUtils.login(username, password, authenticationManager); @@ -52,6 +55,9 @@ } if (user == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "用户名或密码错误"); }else { String jwt = JwtUtils.createToken(username, password); response.setHeader(JwtUtils.getHeader(), jwt); } return user; }