mirror of https://github.com/longtai-cn/hippo4j
parent
1730efafca
commit
9400439700
@ -0,0 +1,81 @@
|
||||
package com.github.dynamic.threadpool.auth.config;
|
||||
|
||||
import com.github.dynamic.threadpool.auth.constant.Constants;
|
||||
import com.github.dynamic.threadpool.auth.filter.JWTAuthenticationFilter;
|
||||
import com.github.dynamic.threadpool.auth.filter.JWTAuthorizationFilter;
|
||||
import com.github.dynamic.threadpool.auth.service.impl.UserDetailsServiceImpl;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
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.EnableWebSecurity;
|
||||
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.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 安全配置.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/11/9 21:10
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
public class GlobalSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Resource
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
@Bean
|
||||
public UserDetailsService customUserService() {
|
||||
return new UserDetailsServiceImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public BCryptPasswordEncoder bCryptPasswordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.addAllowedMethod(Constants.SPLIT_STAR);
|
||||
config.applyPermitDefaultValues();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.cors().and().csrf().disable()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/static/**", "/index.html", "/favicon.ico", "/avatar.jpg").permitAll()
|
||||
.antMatchers("/doc.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs").anonymous()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
|
||||
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(WebSecurity web) throws Exception {
|
||||
web.ignoring().antMatchers("/v1/cs/apps/renew/**", "/v1/cs/apps/register/**", "/v1/cs/configs/**", "/v1/cs/listener/**");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.github.dynamic.threadpool.auth.constant;
|
||||
|
||||
/**
|
||||
* Constants.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/11/9 22:24
|
||||
*/
|
||||
public class Constants {
|
||||
|
||||
public static final String SPLIT_STAR = "*";
|
||||
|
||||
public static final String SPLIT_COMMA = ",";
|
||||
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package com.github.dynamic.threadpool.auth.filter;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.dynamic.threadpool.auth.model.biz.user.JwtUser;
|
||||
import com.github.dynamic.threadpool.auth.model.biz.user.LoginUser;
|
||||
import com.github.dynamic.threadpool.auth.toolkit.JwtTokenUtil;
|
||||
import com.github.dynamic.threadpool.auth.toolkit.ReturnT;
|
||||
import com.github.dynamic.threadpool.common.web.base.Results;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
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.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
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;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.github.dynamic.threadpool.auth.constant.Constants.SPLIT_COMMA;
|
||||
|
||||
/**
|
||||
* JWT authentication filter.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/11/9 22:21
|
||||
*/
|
||||
@Slf4j
|
||||
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
|
||||
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
private ThreadLocal<Integer> rememberMe = new ThreadLocal();
|
||||
|
||||
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
|
||||
this.authenticationManager = authenticationManager;
|
||||
super.setFilterProcessesUrl("/v1/cs/auth/login");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication attemptAuthentication(HttpServletRequest request,
|
||||
HttpServletResponse response) throws AuthenticationException {
|
||||
// 从输入流中获取到登录的信息
|
||||
try {
|
||||
LoginUser loginUser = new ObjectMapper().readValue(request.getInputStream(), LoginUser.class);
|
||||
rememberMe.set(loginUser.getRememberMe());
|
||||
return authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword(), new ArrayList<>())
|
||||
);
|
||||
} catch (IOException e) {
|
||||
logger.error("attemptAuthentication error :{}", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void successfulAuthentication(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain chain,
|
||||
Authentication authResult) throws IOException {
|
||||
JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
|
||||
boolean isRemember = rememberMe.get() == 1;
|
||||
|
||||
String role = "";
|
||||
Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities();
|
||||
for (GrantedAuthority authority : authorities) {
|
||||
role = authority.getAuthority();
|
||||
}
|
||||
|
||||
String token = JwtTokenUtil.createToken(jwtUser.getId(), jwtUser.getUsername(), role, isRemember);
|
||||
response.setHeader("token", JwtTokenUtil.TOKEN_PREFIX + token);
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
Map<String, Object> maps = new HashMap<>();
|
||||
maps.put("data", JwtTokenUtil.TOKEN_PREFIX + token);
|
||||
maps.put("roles", role.split(SPLIT_COMMA));
|
||||
response.getWriter().write(JSONUtil.toJsonStr(Results.success(maps)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.getWriter().write(JSONUtil.toJsonStr(new ReturnT(-1, "Server Error")));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package com.github.dynamic.threadpool.auth.filter;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.github.dynamic.threadpool.auth.toolkit.JwtTokenUtil;
|
||||
import com.github.dynamic.threadpool.common.web.base.Results;
|
||||
import com.github.dynamic.threadpool.common.web.exception.ServiceException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||
|
||||
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.Collections;
|
||||
|
||||
/**
|
||||
* JWT authorization filter.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/11/9 22:21
|
||||
*/
|
||||
@Slf4j
|
||||
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
|
||||
|
||||
public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
|
||||
super(authenticationManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain chain) throws IOException, ServletException {
|
||||
|
||||
String tokenHeader = request.getHeader(JwtTokenUtil.TOKEN_HEADER);
|
||||
// 如果请求头中没有 Authorization 信息则直接放行
|
||||
if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtil.TOKEN_PREFIX)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
// 如果请求头中有 Token, 则进行解析, 并且设置认证信息
|
||||
try {
|
||||
SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
|
||||
} catch (Exception ex) {
|
||||
// 返回 Json 形式的错误信息
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setContentType("application/json; charset=utf-8");
|
||||
response.getWriter().write(JSON.toJSONString(Results.failure("-1", ex.getMessage())));
|
||||
response.getWriter().flush();
|
||||
return;
|
||||
}
|
||||
super.doFilterInternal(request, response, chain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Token 中获取用户信息并新建一个 Token.
|
||||
*
|
||||
* @param tokenHeader
|
||||
* @return
|
||||
*/
|
||||
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
|
||||
String token = tokenHeader.replace(JwtTokenUtil.TOKEN_PREFIX, "");
|
||||
boolean expiration = JwtTokenUtil.isExpiration(token);
|
||||
if (expiration) {
|
||||
throw new ServiceException("登录时间过长,请退出重新登录");
|
||||
}
|
||||
|
||||
String username = JwtTokenUtil.getUsername(token);
|
||||
String role = JwtTokenUtil.getUserRole(token);
|
||||
if (username != null) {
|
||||
return new UsernamePasswordAuthenticationToken(username, null,
|
||||
Collections.singleton(new SimpleGrantedAuthority(role))
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package com.github.dynamic.threadpool.auth.model.biz.user;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Jwt user.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/11/9 22:34
|
||||
*/
|
||||
@Data
|
||||
public class JwtUser implements UserDetails {
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* userName
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* password
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* authorities
|
||||
*/
|
||||
private Collection<? extends GrantedAuthority> authorities;
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.github.dynamic.threadpool.auth.model.biz.user;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Login user.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/11/9 22:41
|
||||
*/
|
||||
@Data
|
||||
public class LoginUser {
|
||||
|
||||
/**
|
||||
* username
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* password
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* rememberMe
|
||||
*/
|
||||
private Integer rememberMe;
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.github.dynamic.threadpool.auth.model.biz.user;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* User req dto.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/11/11 20:30
|
||||
*/
|
||||
@Data
|
||||
public class UserReqDTO extends Page {
|
||||
|
||||
/**
|
||||
* userName
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* password
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* role
|
||||
*/
|
||||
private String role;
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.github.dynamic.threadpool.auth.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.github.dynamic.threadpool.auth.mapper.UserMapper;
|
||||
import com.github.dynamic.threadpool.auth.model.UserInfo;
|
||||
import com.github.dynamic.threadpool.auth.model.biz.user.JwtUser;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* User details service impl.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/11/9 22:26
|
||||
*/
|
||||
public class UserDetailsServiceImpl implements UserDetailsService {
|
||||
|
||||
@Resource
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
|
||||
UserInfo userInfo = userMapper.selectOne(Wrappers.lambdaQuery(UserInfo.class).eq(UserInfo::getUserName, userName));
|
||||
|
||||
JwtUser jwtUser = new JwtUser();
|
||||
jwtUser.setId(userInfo.getId());
|
||||
jwtUser.setUsername(userName);
|
||||
jwtUser.setPassword(userInfo.getPassword());
|
||||
|
||||
Set<SimpleGrantedAuthority> authorities = Collections.singleton(new SimpleGrantedAuthority(userInfo.getRole() + ""));
|
||||
jwtUser.setAuthorities(authorities);
|
||||
|
||||
return jwtUser;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
package com.github.dynamic.threadpool.auth.toolkit;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import static com.github.dynamic.threadpool.auth.constant.Constants.SPLIT_COMMA;
|
||||
|
||||
/**
|
||||
* Jwt token util.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/11/9 22:43
|
||||
*/
|
||||
public class JwtTokenUtil {
|
||||
|
||||
public static final String TOKEN_HEADER = "Authorization";
|
||||
public static final String TOKEN_PREFIX = "Bearer ";
|
||||
|
||||
private static final String SECRET = "Hippo4J_admin";
|
||||
private static final String ISS = "admin";
|
||||
|
||||
/**
|
||||
* 角色的 Key
|
||||
*/
|
||||
private static final String ROLE_CLAIMS = "rol";
|
||||
|
||||
/**
|
||||
* 过期时间是 3600 秒, 既 24 小时
|
||||
*/
|
||||
private static final long EXPIRATION = 86400L;
|
||||
|
||||
/**
|
||||
* 选择了记住我之后的过期时间为 7 天
|
||||
*/
|
||||
private static final long EXPIRATION_REMEMBER = 7 * EXPIRATION;
|
||||
|
||||
/**
|
||||
* 创建 Token.
|
||||
*
|
||||
* @param id
|
||||
* @param username
|
||||
* @param role
|
||||
* @param isRememberMe
|
||||
* @return
|
||||
*/
|
||||
public static String createToken(Long id, String username, String role, boolean isRememberMe) {
|
||||
long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.put(ROLE_CLAIMS, role);
|
||||
return Jwts.builder()
|
||||
.signWith(SignatureAlgorithm.HS512, SECRET)
|
||||
.setClaims(map)
|
||||
.setIssuer(ISS)
|
||||
.setSubject(id + SPLIT_COMMA + username)
|
||||
.setIssuedAt(new Date())
|
||||
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* Token 中获取用户名.
|
||||
*
|
||||
* @param token
|
||||
* @return
|
||||
*/
|
||||
public static String getUsername(String token) {
|
||||
List<String> userInfo = Arrays.asList(getTokenBody(token).getSubject().split(SPLIT_COMMA));
|
||||
return userInfo.get(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Token 中获取用户名.
|
||||
*
|
||||
* @param token
|
||||
* @return
|
||||
*/
|
||||
public static Integer getUserId(String token) {
|
||||
List<String> userInfo = Arrays.asList(getTokenBody(token).getSubject().split(SPLIT_COMMA));
|
||||
return Integer.parseInt(userInfo.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户角色.
|
||||
*
|
||||
* @param token
|
||||
* @return
|
||||
*/
|
||||
public static String getUserRole(String token) {
|
||||
return (String) getTokenBody(token).get(ROLE_CLAIMS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否已过期.
|
||||
*
|
||||
* @param token
|
||||
* @return
|
||||
*/
|
||||
public static boolean isExpiration(String token) {
|
||||
try {
|
||||
return getTokenBody(token).getExpiration().before(new Date());
|
||||
} catch (ExpiredJwtException e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static Claims getTokenBody(String token) {
|
||||
return Jwts.parser()
|
||||
.setSigningKey(SECRET)
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.github.dynamic.threadpool.auth.toolkit;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* ReturnT.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/11/10 00:00
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class ReturnT<T> implements Serializable {
|
||||
|
||||
public static final long serialVersionUID = 42L;
|
||||
|
||||
public static final int SUCCESS_CODE = 200;
|
||||
public static final int FAIL_CODE = 500;
|
||||
|
||||
public static final ReturnT<String> SUCCESS = new ReturnT<>(null);
|
||||
public static final ReturnT<String> FAIL = new ReturnT<>(FAIL_CODE, null);
|
||||
|
||||
private int code;
|
||||
private String msg;
|
||||
private T content;
|
||||
|
||||
public ReturnT(int code, String msg) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public ReturnT(T content) {
|
||||
this.code = SUCCESS_CODE;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in new issue