Compare commits
3 Commits
Author | SHA1 | Date |
---|---|---|
燕鹏 | 38cf2ae7f5 | 4 years ago |
燕鹏 | 9d80a93fd5 | 4 years ago |
mail_yanpeng@163.com | 341eea06fa | 4 years ago |
15 changed files with 487 additions and 100 deletions
@ -0,0 +1,70 @@ |
|||
package com.aiprose.scauth.conf; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.context.annotation.Bean; |
|||
import org.springframework.context.annotation.Configuration; |
|||
import org.springframework.security.authentication.AuthenticationManager; |
|||
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; |
|||
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; |
|||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; |
|||
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; |
|||
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; |
|||
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; |
|||
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; |
|||
|
|||
/** |
|||
* @author nelson |
|||
* @desc TODO |
|||
* @company 北京中经网软件有限公司 |
|||
* @date 2020/11/30 11:10 |
|||
* @since 1.0 |
|||
*/ |
|||
@Configuration |
|||
@EnableAuthorizationServer |
|||
public class AuthcServerConfig extends AuthorizationServerConfigurerAdapter { |
|||
|
|||
@Autowired |
|||
private AuthenticationManager authenticationManager; |
|||
|
|||
|
|||
/** |
|||
* 配置一个客户端 |
|||
* |
|||
* 既可以通过授权码方式获取令牌,也可以通过密码方式获取令牌 |
|||
*/ |
|||
@Override |
|||
public void configure(ClientDetailsServiceConfigurer clients) throws Exception { |
|||
clients.inMemory() |
|||
.withClient("clientId") |
|||
.secret("secret") |
|||
.authorizedGrantTypes("authorization_code","password","refresh_token") |
|||
.scopes("all") |
|||
.redirectUris("http://localhost:8015/"); |
|||
} |
|||
|
|||
/** 配置token管理 */ |
|||
@Override |
|||
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { |
|||
endpoints.tokenStore(new InMemoryTokenStore()) |
|||
.accessTokenConverter(accessTokenConverter()) |
|||
.authenticationManager(authenticationManager) |
|||
.reuseRefreshTokens(false); |
|||
} |
|||
|
|||
/** 配置jwt转换器 */ |
|||
@Bean |
|||
public JwtAccessTokenConverter accessTokenConverter() { |
|||
JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); |
|||
converter.setSigningKey("secret"); |
|||
return converter; |
|||
} |
|||
|
|||
|
|||
|
|||
@Override |
|||
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { |
|||
security.tokenKeyAccess("permitAll()") //允许所有人请求令牌
|
|||
.checkTokenAccess("isAuthenticated()") //已验证的客户端才能请求check_token端点
|
|||
.allowFormAuthenticationForClients(); |
|||
} |
|||
} |
@ -0,0 +1,89 @@ |
|||
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.authentication.AuthenticationManager; |
|||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; |
|||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; |
|||
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.authentication.UsernamePasswordAuthenticationFilter; |
|||
import org.springframework.web.servlet.config.annotation.CorsRegistry; |
|||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; |
|||
|
|||
/** |
|||
* @author nelson |
|||
* @desc script配置 |
|||
* @company 北京中经网软件有限公司 |
|||
* @date 2020/11/27 15:32 |
|||
* @since 1.0 |
|||
*/ |
|||
@Configuration |
|||
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true) |
|||
public class WebAuthcSecurityConfig extends WebSecurityConfigurerAdapter { |
|||
@Autowired |
|||
private IUserService userService; |
|||
|
|||
@Override |
|||
public void configure(WebSecurity web) throws Exception { |
|||
web.ignoring().antMatchers("/v3/api-docs/**", "/swagger-resources/**", "/swagger-ui/**","/swagger-ui/index.html"); |
|||
} |
|||
|
|||
@Override |
|||
protected void configure(HttpSecurity http) throws Exception { |
|||
http.cors().and().csrf().disable(); |
|||
|
|||
// 授权配置
|
|||
http.authorizeRequests().antMatchers("/oauth/**").permitAll().anyRequest().authenticated(); |
|||
// 配置登录
|
|||
http.formLogin().permitAll(); |
|||
|
|||
//登录过期、 未登录
|
|||
http.exceptionHandling().authenticationEntryPoint(new LoginExpireHandler()); |
|||
// 配置登录失败后的操作
|
|||
http.formLogin().failureHandler(new LoginFailureHandler()); |
|||
// 配置登录成功后的操作
|
|||
http.formLogin().successHandler(new LoginSuccessHandler()); |
|||
|
|||
//权限不足
|
|||
http.exceptionHandling().accessDeniedHandler(new AuthLimitHandler()); |
|||
|
|||
// 登出授权
|
|||
// http.logout().permitAll();
|
|||
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()); |
|||
} |
|||
|
|||
@Bean |
|||
public WebMvcConfigurer corsConfigurer() { |
|||
return new WebMvcConfigurer() { |
|||
@Override |
|||
public void addCorsMappings(CorsRegistry registry) { |
|||
registry.addMapping("/**"); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
@Bean |
|||
@Override |
|||
public AuthenticationManager authenticationManagerBean() throws Exception { |
|||
return super.authenticationManagerBean(); |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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\": \"登录过期或未登录\"}"); |
|||
} |
|||
} |
@ -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\": \"登录失败\"}"); |
|||
} |
|||
} |
@ -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]); |
|||
} |
|||
} |
@ -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…
Reference in new issue