Browse Source

token

前后分离
mail_yanpeng@163.com 4 years ago
parent
commit
341eea06fa
  1. 1
      build.gradle
  2. 108
      src/main/java/com/aiprose/scauth/conf/WebSecurityConfig.java
  3. 9
      src/main/java/com/aiprose/scauth/controller/LoginController.java
  4. 26
      src/main/java/com/aiprose/scauth/entity/Jwt.java
  5. 2
      src/main/java/com/aiprose/scauth/entity/Menu.java
  6. 2
      src/main/java/com/aiprose/scauth/entity/Role.java
  7. 13
      src/main/java/com/aiprose/scauth/entity/User.java
  8. 2
      src/main/java/com/aiprose/scauth/entity/UserRole.java
  9. 48
      src/main/java/com/aiprose/scauth/filter/JWTAuthenticationFilter.java
  10. 28
      src/main/java/com/aiprose/scauth/handler/LoginExpireHandler.java
  11. 28
      src/main/java/com/aiprose/scauth/handler/LoginFailureHandler.java
  12. 38
      src/main/java/com/aiprose/scauth/handler/LoginSuccessHandler.java
  13. 80
      src/main/java/com/aiprose/scauth/util/JwtUtils.java
  14. 30
      src/main/resources/templates/login.html

1
build.gradle

@ -36,6 +36,7 @@ dependencies {
implementation 'com.aiprose:jpa-common-utils:2.3.4' implementation 'com.aiprose:jpa-common-utils:2.3.4'
implementation 'org.apache.commons:commons-lang3:3.10' implementation 'org.apache.commons:commons-lang3:3.10'
implementation 'com.alibaba:fastjson:1.2.47'
} }
test { test {

108
src/main/java/com/aiprose/scauth/conf/WebSecurityConfig.java

@ -1,10 +1,12 @@
package com.aiprose.scauth.conf; package com.aiprose.scauth.conf;
import com.aiprose.scauth.entity.User; import com.aiprose.scauth.entity.User;
import com.aiprose.scauth.filter.JWTAuthenticationFilter;
import com.aiprose.scauth.handler.*; import com.aiprose.scauth.handler.*;
import com.aiprose.scauth.service.IUserService; import com.aiprose.scauth.service.IUserService;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.AccessDecisionVoter;
@ -17,11 +19,15 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.expression.WebExpressionVoter; import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -34,7 +40,7 @@ import java.util.List;
* @since 1.0 * @since 1.0
*/ */
@Configuration @Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true) //@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired @Autowired
private IUserService userService; private IUserService userService;
@ -45,21 +51,23 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override @Override
public void configure(WebSecurity web) throws Exception { public void configure(WebSecurity web) throws Exception {
// super.configure(web); // super.configure(web);
web.ignoring().antMatchers("login", "/v2/api-docs/**", "/swagger-resources/**", "/swagger-ui.html");
} }
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); http.cors().and().csrf().disable();
// 配置记住我的参数和记住我处理类 // 授权配置
http.rememberMe() http.authorizeRequests().anyRequest().authenticated();
.tokenRepository(remeberMeHandler)
.tokenValiditySeconds(60*60*24)
.userDetailsService(userDetailsService());
// 配置登录页面 // 配置登录
http.formLogin().loginPage("/login").permitAll(); http.formLogin().usernameParameter("username").passwordParameter("password").loginProcessingUrl("/login");
//登录过期、 未登录
http.exceptionHandling().authenticationEntryPoint(new LoginExpireHandler());
// 配置登录失败后的操作
http.formLogin().failureHandler(new LoginFailureHandler());
// 配置登录成功后的操作 // 配置登录成功后的操作
http.formLogin().successHandler(new LoginSuccessHandler()); http.formLogin().successHandler(new LoginSuccessHandler());
@ -67,43 +75,37 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
http.exceptionHandling().accessDeniedHandler(new AuthLimitHandler()); http.exceptionHandling().accessDeniedHandler(new AuthLimitHandler());
// 登出授权 // 登出授权
http.logout().permitAll(); // http.logout().permitAll();
// 授权配置 http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests()
/* 所有静态文件可以访问 */ /* 配置token验证过滤器 */
.antMatchers("/js/**","/css/**","/images/**").permitAll() http.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
/* 所有 以/ad 开头的 广告页面可以访问 */
.antMatchers("/ad/**").permitAll()
.antMatchers("/user/**","/role/**").permitAll()
/* 动态url权限 */
.withObjectPostProcessor(new DefinedObjectPostProcessor())
/* url决策 */
.accessDecisionManager(accessDecisionManager())
.anyRequest().authenticated();
} }
@Override @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder()); auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
// auth.passwordEncoder(new BCryptPasswordEncoder()); }
// .withUser("nelson").password(new BCryptPasswordEncoder().encode("123456")).roles("admin")
// .and() @Bean
// .withUser("yasaka").password(new BCryptPasswordEncoder().encode("123456")).roles("user") public WebMvcConfigurer corsConfigurer() {
// .and() return new WebMvcConfigurer() {
// .withUser("one").password(new BCryptPasswordEncoder().encode("123456")).roles("gest") @Override
// .and() public void addCorsMappings(CorsRegistry registry) {
// .withUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("root"); registry.addMapping("/**");
}
};
} }
@Override @Override
protected UserDetailsService userDetailsService() { protected UserDetailsService userDetailsService() {
return username -> { return username -> {
if(StringUtils.isBlank(username)){ if (StringUtils.isBlank(username)) {
throw new UsernameNotFoundException("用户名为空"); throw new UsernameNotFoundException("用户名为空");
} }
User user = userService.findByUsernameAndRole(username); User user = userService.findByUsernameAndRole(username);
if(user == null){ if (user == null) {
throw new UsernameNotFoundException("用户不存在"); throw new UsernameNotFoundException("用户不存在");
} }
return user; return user;
@ -117,25 +119,25 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
* *
* 决策管理 * 决策管理
*/ */
private AccessDecisionManager accessDecisionManager() { // private AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<>(); // List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<>();
decisionVoters.add(new WebExpressionVoter()); // decisionVoters.add(new WebExpressionVoter());
decisionVoters.add(new AuthenticatedVoter()); // decisionVoters.add(new AuthenticatedVoter());
decisionVoters.add(new RoleVoter()); // decisionVoters.add(new RoleVoter());
/* 路由权限管理 */ // /* 路由权限管理 */
decisionVoters.add(new UrlRoleAuthHandler()); // decisionVoters.add(new UrlRoleAuthHandler());
return new UnanimousBased(decisionVoters); // return new UnanimousBased(decisionVoters);
} // }
@Autowired // @Autowired
private UrlRolesFilterHandler urlRolesFilterHandler; // private UrlRolesFilterHandler urlRolesFilterHandler;
class DefinedObjectPostProcessor implements ObjectPostProcessor<FilterSecurityInterceptor> { // class DefinedObjectPostProcessor implements ObjectPostProcessor<FilterSecurityInterceptor> {
@Override // @Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) { // public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setSecurityMetadataSource(urlRolesFilterHandler); // object.setSecurityMetadataSource(urlRolesFilterHandler);
return object; // return object;
} // }
} // }
} }

9
src/main/java/com/aiprose/scauth/controller/LoginController.java

@ -5,9 +5,8 @@ import org.springframework.web.bind.annotation.GetMapping;
@Controller @Controller
public class LoginController { public class LoginController {
// @GetMapping("/login")
@GetMapping("/login") // public String login(){
public String login(){ // return "login";
return "login"; // }
}
} }

26
src/main/java/com/aiprose/scauth/entity/Jwt.java

@ -0,0 +1,26 @@
package com.aiprose.scauth.entity;
import com.aiprose.scauth.util.JwtUtils;
import lombok.Data;
@Data
public class Jwt {
/* 头部 */
private String header;
/* 负载 */
private String payload;
/* 签名 */
private String signature;
public Jwt(String payload) throws Exception {
this.header = JwtUtils.encode(JwtUtils.DEFAULT_HEADER);
this.payload = JwtUtils.encode(payload);
this.signature = JwtUtils.getSignature(payload);
}
/* jwt最终结果 */
@Override
public String toString() {
return header + "." + payload + "." + signature;
}
}

2
src/main/java/com/aiprose/scauth/entity/Menu.java

@ -13,7 +13,7 @@ import javax.persistence.Table;
* @date 2020/11/27 17:04 * @date 2020/11/27 17:04
* @since 1.0 * @since 1.0
*/ */
@Data @Data
@Entity @Entity
@Table(name = "sys_menu") @Table(name = "sys_menu")
public class Menu extends IDEntity { public class Menu extends IDEntity {

2
src/main/java/com/aiprose/scauth/entity/Role.java

@ -1,6 +1,7 @@
package com.aiprose.scauth.entity; package com.aiprose.scauth.entity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import javax.persistence.Entity; import javax.persistence.Entity;
@ -15,6 +16,7 @@ import javax.persistence.Table;
*/ */
@Data @Data
@Entity @Entity
@EqualsAndHashCode(callSuper=false)
@Table(name="sys_role") @Table(name="sys_role")
public class Role extends IDEntity implements GrantedAuthority { public class Role extends IDEntity implements GrantedAuthority {
private String role; private String role;

13
src/main/java/com/aiprose/scauth/entity/User.java

@ -26,6 +26,19 @@ public class User extends IDEntity implements UserDetails {
private String username; private String username;
private String password; private String password;
/* 指示是否未过期的用户的凭据(密码),过期的凭据防止认证 默认true 默认表示未过期 */
@Transient
private boolean credentialsNonExpired = true;
//账户是否未过期,过期无法验证 默认true表示未过期
private boolean accountNonExpired = true;
//用户是未被锁定,锁定的用户无法进行身份验证 默认true表示未锁定
private boolean accountNonLocked = true;
//是否可用 ,禁用的用户不能身份验证 默认true表示可用
private boolean enabled = true;
@Transient @Transient
private List<Role> roles; private List<Role> roles;

2
src/main/java/com/aiprose/scauth/entity/UserRole.java

@ -1,6 +1,7 @@
package com.aiprose.scauth.entity; package com.aiprose.scauth.entity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Table; import javax.persistence.Table;
@ -14,6 +15,7 @@ import javax.persistence.Table;
*/ */
@Data @Data
@Entity @Entity
@EqualsAndHashCode(callSuper=false)
@Table(name="sys_user_role") @Table(name="sys_user_role")
public class UserRole extends IDEntity{ public class UserRole extends IDEntity{
private Integer sysUserId; private Integer sysUserId;

48
src/main/java/com/aiprose/scauth/filter/JWTAuthenticationFilter.java

@ -0,0 +1,48 @@
package com.aiprose.scauth.filter;
import com.aiprose.scauth.entity.User;
import com.aiprose.scauth.util.JwtUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JWTAuthenticationFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String token = req.getHeader(JwtUtils.HEADER_TOKEN_NAME);
/* token为null直接走登录的过滤器,不为空走下面 */
if (token!=null&&token.trim().length()>0) {
String tokenBody = null;
try {
tokenBody = JwtUtils.testJwt(token);
} catch (Exception e) {
e.printStackTrace();
}
/* 从token中取出用户信息,放在上下文中 */
if (tokenBody!=null&&tokenBody.trim().length()>0){
JSONObject user = JSON.parseObject(tokenBody).getJSONObject("user");
User sysUser = JSON.toJavaObject(user, User.class);
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(sysUser,null,sysUser.getAuthorities()));
}else{
HttpServletResponse res = (HttpServletResponse) response;
res.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
res.getWriter().write("{\"code\": \"405\", \"msg\": \"token错误\"}");
return;
}
}
filterChain.doFilter(request, response);
}
}

28
src/main/java/com/aiprose/scauth/handler/LoginExpireHandler.java

@ -0,0 +1,28 @@
package com.aiprose.scauth.handler;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author nelson
* @desc TODO
* @company 北京中经网软件有限公司
* @date 2020/11/27 16:15
* @since 1.0
*/
public class LoginExpireHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
authException.printStackTrace();
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getWriter().write("{\"code\": \"401\", \"msg\": \"登录过期或未登录\"}");
}
}

28
src/main/java/com/aiprose/scauth/handler/LoginFailureHandler.java

@ -0,0 +1,28 @@
package com.aiprose.scauth.handler;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author nelson
* @desc TODO
* @company 北京中经网软件有限公司
* @date 2020/11/27 16:15
* @since 1.0
*/
public class LoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getWriter().write("{\"code\": \"500\", \"msg\": \"登录失败\"}");
}
}

38
src/main/java/com/aiprose/scauth/handler/LoginSuccessHandler.java

@ -1,5 +1,10 @@
package com.aiprose.scauth.handler; package com.aiprose.scauth.handler;
import com.aiprose.scauth.entity.Jwt;
import com.aiprose.scauth.entity.User;
import com.aiprose.scauth.util.JwtUtils;
import com.alibaba.fastjson.JSONObject;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@ -18,11 +23,32 @@ import java.io.IOException;
public class LoginSuccessHandler implements AuthenticationSuccessHandler { public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Override @Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("login success"); // 获取登录成功信息
System.out.println(authentication.getDetails()); response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
System.out.println(authentication.getAuthorities()); boolean loginBoolean = true;
System.out.println(authentication.getCredentials()); User user = (User) authentication.getPrincipal();
System.out.println(authentication.getPrincipal()); user.setPassword(null);
response.sendRedirect("/home"); long now = System.currentTimeMillis();
JSONObject payload = new JSONObject();
payload.put("iss", "sys"); //签发人
payload.put("aud", user.getUsername()); //受众
payload.put("exp", now + JwtUtils.EXPIRE_TIME); //过期时间
payload.put("nbf", now); //生效时间
payload.put("iat", now); //签发时间
payload.put("jti", user.getId()); //编号
payload.put("sub", "JWT-TEST"); //主题
payload.put("user", user); //用户对象
try {
String token = new Jwt(payload.toJSONString()).toString();
response.setHeader(JwtUtils.HEADER_TOKEN_NAME, token);
if (loginBoolean) {
response.getWriter().write("{\"code\": \"200\", \"msg\": \"登录成功\", \"token\": \"" + token + "\"}");
} else {
response.getWriter().write("{\"code\": \"500\", \"msg\": \"登录失败\"}");
}
} catch (Exception e) {
loginBoolean = false;
}
} }
} }

80
src/main/java/com/aiprose/scauth/util/JwtUtils.java

@ -0,0 +1,80 @@
package com.aiprose.scauth.util;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class JwtUtils {
/* 默认head */
public static final String DEFAULT_HEADER = "{\"alg\": \"HS256\",\"typ\": \"JWT\"}";
/* HmacSHA256 加密算法 秘钥 */
public static final String SECRET = "12345";
/* token有效时间 1天 */
public static final long EXPIRE_TIME = 1000 * 60 * 60 * 24;
/* token在header中的名字 */
public static final String HEADER_TOKEN_NAME = "Authorization";
/**
* Base64URL 编码
*/
public static String encode(String input) {
return Base64.getUrlEncoder().encodeToString(input.getBytes());
}
/**
* Base64URL 解码
*/
public static String decode(String input) {
return new String(Base64.getUrlDecoder().decode(input));
}
/**
* HmacSHA256 加密算法
*
* @param data 要加密的数据
* @param secret 秘钥
*/
public static String HMACSHA256(String data, String secret) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3);
}
return sb.toString().toUpperCase();
}
/**
* 获取签名
*/
public static String getSignature(String payload) throws Exception {
return HMACSHA256(encode(DEFAULT_HEADER) + "." + encode(payload), SECRET);
}
/**
* 验证jwt正确返回载体数据错误返回null
*
* @param jwt
*/
public static String testJwt(String jwt) throws Exception {
String[] jwts = jwt.split("\\.");
/* 验证签名 */
if (!HMACSHA256(jwts[0] + "." + jwts[1], SECRET).equals(jwts[2])) {
return null;
}
/* 验证头部信息 */
if (!jwts[0].equals(encode(DEFAULT_HEADER))) {
return null;
}
return decode(jwts[1]);
}
}

30
src/main/resources/templates/login.html

@ -1,30 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head>
<title>请登录</title>
</head>
<body>
<div>
<form th:action="@{/login}" method="post" action="/login">
<p>
<span>用户名:</span>
<input type="text" id="username" name="username">
</p>
<p>
<span>密码:</span>
<input type="password" id="password" name="password">
</p>
<p>
<span>记住我:</span>
<input type="checkbox" name="remember-me" value="true">
</p>
<!-- 不使用 th:action 属性 和 不关闭csrf 的情况下,需要放开下面的标签 -->
<!--<input th:name="${_csrf.parameterName}" type="hidden" th:value="${_csrf.token}"/>-->
<input type="submit" value="登录" />
</form>
</div>
</body>
</html>
Loading…
Cancel
Save