Bläddra i källkod

token

前后分离
mail_yanpeng@163.com 4 år sedan
förälder
incheckning
341eea06fa
  1. 1
      build.gradle
  2. 110
      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 'org.apache.commons:commons-lang3:3.10'
implementation 'com.alibaba:fastjson:1.2.47'
}
test {

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

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

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

@ -5,9 +5,8 @@ import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
@GetMapping("/login")
public String login(){
return "login";
}
// @GetMapping("/login")
// public String 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
* @since 1.0
*/
@Data
@Data
@Entity
@Table(name = "sys_menu")
public class Menu extends IDEntity {

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

@ -1,6 +1,7 @@
package com.aiprose.scauth.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.security.core.GrantedAuthority;
import javax.persistence.Entity;
@ -15,6 +16,7 @@ import javax.persistence.Table;
*/
@Data
@Entity
@EqualsAndHashCode(callSuper=false)
@Table(name="sys_role")
public class Role extends IDEntity implements GrantedAuthority {
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 password;
/* 指示是否未过期的用户的凭据(密码),过期的凭据防止认证 默认true 默认表示未过期 */
@Transient
private boolean credentialsNonExpired = true;
//账户是否未过期,过期无法验证 默认true表示未过期
private boolean accountNonExpired = true;
//用户是未被锁定,锁定的用户无法进行身份验证 默认true表示未锁定
private boolean accountNonLocked = true;
//是否可用 ,禁用的用户不能身份验证 默认true表示可用
private boolean enabled = true;
@Transient
private List<Role> roles;

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

@ -1,6 +1,7 @@
package com.aiprose.scauth.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.persistence.Entity;
import javax.persistence.Table;
@ -14,6 +15,7 @@ import javax.persistence.Table;
*/
@Data
@Entity
@EqualsAndHashCode(callSuper=false)
@Table(name="sys_user_role")
public class UserRole extends IDEntity{
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;
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.web.authentication.AuthenticationSuccessHandler;
@ -18,11 +23,32 @@ import java.io.IOException;
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("login success");
System.out.println(authentication.getDetails());
System.out.println(authentication.getAuthorities());
System.out.println(authentication.getCredentials());
System.out.println(authentication.getPrincipal());
response.sendRedirect("/home");
// 获取登录成功信息
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
boolean loginBoolean = true;
User user = (User) authentication.getPrincipal();
user.setPassword(null);
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>
Laddar…
Avbryt
Spara