springSecurity基本使用

一、pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>

<groupId>com.ycs.study</groupId>
<artifactId>springboot2-springsecurity-jwt</artifactId>
<version>1.0-SNAPSHOT</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.13.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--spring security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>


<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- <scope>provided</scope> -->
</dependency>

<!-- commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- <configuration>
<jvmArguments>
-Dfile.encoding=UTF-8 -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005
</jvmArguments>
</configuration>-->
</plugin>
</plugins>
</build>
</project>
二、配置文件
application.yml
spring:
profiles:
active: dev

application-dev.yml
# port
server:
port: 8081
servlet:
context-path: /my
#出现错误时, 直接抛出异常, 配合全局异常返回自定义信息,如404
#spring:
# mvc:
# throw-exception-if-no-handler-found: true
# resources:
# add-mappings: false
knife4j:
enable: true

三、统一返回结果类
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Objects;

public class ResultData extends LinkedHashMap<String, Object> implements Serializable {

/**
* 状态码
*/
public static final String CODE_TAG = "code";

/**
* 返回内容
*/
public static final String MSG_TAG = "msg";

/**
* 数据对象
*/
public static final String DATA_TAG = "data";

/**
* 返回成功和失败的标记
*/
public static final String SUCCESS_TAG = "success";

/**
* 返回数据条数
*/
public static final String COUNT_TAG = "count";

public ResultData() {
}

;

/**
* @param code
* @param msg
*/
public ResultData(boolean success, int code, String msg) {
super.put(SUCCESS_TAG, success);
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
}

/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
* @param data 数据对象 支持返回为null
*/
public ResultData(boolean success, int code, String msg, Object data) {
super.put(SUCCESS_TAG, success);
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
if (Objects.nonNull(data)) {
super.put(DATA_TAG, data);
}
}

/**
* 针对分页返回总记录数
*
* @param success
* @param code
* @param msg
* @param data
* @param count
*/
public ResultData(boolean success, int code, String msg, Object data, long count) {
super.put(SUCCESS_TAG, success);
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
if (Objects.nonNull(data)) {
super.put(DATA_TAG, data);
super.put(COUNT_TAG, count);
}
}

/**
* 返回成功消息
*
* @return 成功消息
*/
public static ResultData success() {
return ResultData.success("操作成功", null);
}

public static ResultData success(Object data) {
return ResultData.success("操作成功", data);
}

public static ResultData success(String msg, Object data) {
return new ResultData(true, 0, msg, data);
}

public static ResultData success(int code, String msg, Object data) {
return new ResultData(true, code, msg, data);
}

public static ResultData success(String msg, Object data, long count) {
return new ResultData(true, 0, msg, data, count);
}

public static ResultData error() {
return ResultData.error("操作失败");
}

public static ResultData error(String msg) {
return ResultData.error(-1, msg);
}

public static ResultData error(String msg, Object data) {
return new ResultData(false, -1, msg, data);
}

public static ResultData error(int code, String msg) {
return new ResultData(false, code, msg);
}
}

四、实体类
@Data
public class SysRole implements Serializable {
private Long id;

private String roleName;

public SysRole(Long id, String roleName) {
this.id = id;
this.roleName = roleName;
}
}


public class SysUser implements UserDetails {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}

@Override
public String getPassword() {
return password;
}

@Override
public String getUsername() {
return userName;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}

private Long id;

private String userName;

private String password;

private List<SysRole> roles;

Collection<? extends GrantedAuthority> authorities;

public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
this.authorities = authorities;
}

public List<SysRole> getRoles() {
return roles;
}

public SysUser(Long id, String userName, String password, List<SysRole> roles) {
this.id = id;
this.userName = userName;
this.password = password;
this.roles = roles;
}
}

五、初始化数据、
public class InitData {

public static final Set<SysUser> SYS_USERS = new HashSet<>();

public static final Set<SysRole> SYS_ROLES = new HashSet<>();

static {
SYS_ROLES.add(new SysRole(1L, "JAVA"));
SYS_ROLES.add(new SysRole(2L, "DOCKER"));
SYS_ROLES.add(new SysRole(3L, "PHP"));
SYS_ROLES.add(new SysRole(4L, "PYTHON"));
SYS_ROLES.add(new SysRole(5L, "ENTOS"));
}

static {
SYS_USERS.add(new SysUser(1L, "user", "9943d8ef6ceec647d7b2ccc504c0081a", SYS_ROLES.stream()
.filter(o -> StringUtils.equalsAny(o.getRoleName(), "JAVA", "DOCKER"))
.collect(Collectors.toList())));
SYS_USERS.add(new SysUser(2L, "test", "9943d8ef6ceec647d7b2ccc504c0081a", SYS_ROLES.stream()
.filter(o -> StringUtils.equalsAny(o.getRoleName(), "PHP", "DOCKER"))
.collect(Collectors.toList())));
SYS_USERS.add(new SysUser(3L, "admin", "9943d8ef6ceec647d7b2ccc504c0081a", SYS_ROLES.stream()
.filter(o -> StringUtils.equalsAny(o.getRoleName(), "PYTHON", "CENTOS"))
.collect(Collectors.toList())));
}

}

六、配置类
@Configuration
@EnableSwagger2
public class Knife4jConfiguration {

@Bean(value = "defaultApi3")
public Docket defaultApi2() {
Docket docket=new Docket(DocumentationType.OAS_30)
.apiInfo(new ApiInfoBuilder()
.title("Spring Security JWT 整合demo")
.description("# swagger-bootstrap-ui-demo RESTful APIs")
.termsOfServiceUrl("http://www.xx.com/")
.contact(new Contact("Kevin Yu", "http://example.com", "624249507@qq.com"))
.version("1.0")
.build())
//分组名称
.groupName("3.X版本")
.select()
//这里指定Controller扫描包路径
.apis(RequestHandlerSelectors.basePackage("com.ycs.study.web.controller"))
.paths(PathSelectors.any())
.build();
return docket;
}

private ApiInfo apiInfo(String title, String desc) {
return new ApiInfoBuilder()
.title(title)
.description(desc)
//服务条款网址
.termsOfServiceUrl("http://127.0.0.1:9091/cim-sgsc")
.version("1.0")
.contact(new Contact("Kevin Yu", "", "624249507@qq.com"))
.build();
}
}



@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");

registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");

registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}

@Override
public void addCorsMappings(CorsRegistry registry) {
// 允许跨域访问资源定义: /api/ 所有资源
registry.addMapping("/**")
// 只允许本地的9000端口访问
.allowedOrigins("*")
// .allowedHeaders("*")
// 允许发送Cookie
.allowCredentials(true)
// 允许所有方法
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS");
// .allowedMethods("*");
}
}


sercurity配置类

@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private AppAuthenticationSuccessHandler authenticationSuccessHandler;

@Autowired
private AccessDeniedAuthenticationHandler accessDeniedAuthenticationHandler;

@Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;

@Autowired
private UserService userService;

@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

@Bean
public PasswordEncoder passwordEncoder() {
return new PasswordEncoder() {
@Override
public String encode(CharSequence rawPassword) {
return MD5Util.encode(rawPassword.toString());
}

@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(this.encode(rawPassword.toString()));
}
};
}

/**
* 注入身份管理器bean
*
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

/**
* 把userService 放入AuthenticationManagerBuilder 里
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 加入自定义认证, 无需配置userDetailsService,否则会执行默认的provider
// auth.authenticationProvider(myAuthenticationProvider());
/* auth.userDetailsService(userService)
.passwordEncoder(passwordEncoder());*/
auth.authenticationProvider(daoAuthenticationProvider());
}

/**
* 解决异常抛出被替换的问题
*
* @return
*/
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
daoAuthenticationProvider.setUserDetailsService(userService);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return daoAuthenticationProvider;
}

@Override
protected void configure(HttpSecurity http) throws Exception {
//http.httpBasic() //httpBasic 登录
http
/*
---以下不适合前后端分离项目---
//.formLogin()
//指定自定义登录页面,不指定则使用sucerity自带的默认配置的 /login, 跳转到到默认的登录页面
// .loginPage("/toLogin")
// 自定义登录路径 登录请求url-form表单的action
//.loginProcessingUrl("/authentication/form1")
// 自定义登录失败处理
//.failureHandler(failureAuthenticationHandler) // formLogin专用
// 自定义失败处理页面
//.failureUrl("/login?error")
// 自定义登录成功处理
//.successHandler(authenticationSuccessHandler) // formLogin专用
//.and()
---以上不适合前后端分离项目---
*/.logout()
.logoutUrl("/logout")
.and()
.authorizeRequests()// 对请求授权
// 这些请求不需要身份认证,其他请求需要认证
.antMatchers("/toLogin", "/authentication/require", "/test", "/error", "/authentication/form1")
.permitAll() //这些请求不需要验证
// ============权限配置start===========
// hasRole 用户存的角色名需要带ROLE_, 此处去掉前缀,这个确实不友好,换成hasAuthority
// hasAuthority([authority]) 等同于hasRole,但不会带有ROLE_前缀,不管是Role还是Authority最后都调用hasAuthorityName
// .antMatchers("/docker").hasRole("DOCKER") //用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀)
// .antMatchers("/java").hasRole("JAVA")
// .antMatchers("/php").hasRole("PHP")
// .antMatchers("/python").hasRole("PYTHON")
// .antMatchers("/docker").hasAuthority("DOCKER") //用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀)
// .antMatchers("/java").hasAuthority("JAVA")
// .antMatchers("/php").hasAuthority("PHP")
// .antMatchers("/python").hasAuthority("PYTHON")
// // 自定义权限验证逻辑, 此处硬编码,不灵活,使用 @PreAuthorize("@testPermissionEvaluator.check(authentication)")
// .antMatchers("/custom")
// .access("@testPermissionEvaluator.check(authentication)")
//============权限配置end=============
.anyRequest() // 任何请求
.authenticated() // 都需要身份认证
.and()
.exceptionHandling()
// 用来解决认证过的用户访问无权限资源时的异常,但和全局异常处理冲突(全局异常捕获到异常Exception.class时会不走该handler)
.accessDeniedHandler(accessDeniedAuthenticationHandler) // 权限绝访问拒处理器,默认是跳转页面,此处是返回json数据
// 用来解决匿名用户访问无权限资源时的异常(未认证)
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and()
.csrf()
.disable()// 禁用session了没有csrf攻击了
.sessionManagement()
// 禁用session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

// --rememberMe start--
/*
.and()
.rememberMe()
.userDetailsService(userService)
.rememberMeCookieName("remember-ME") // 自定义cookie名
.tokenValiditySeconds(3600) //cookie存活时间*/
// --rememberMe end--

}

@Override
public void configure(WebSecurity web) throws Exception {
// 设置拦截忽略文件夹,可以对静态资源放行
web.ignoring()
.antMatchers("/css/**", "/js/**", "/doc.html", "/swagger**/**", "/webjars/**", "/doc.html", "/v2/**",
"/v3/**", "/favicon.ico");
}

}

七、无权限匿名访问处理配置类

/**
* 认证但没有权限访问时走该逻辑
* security没有权限的时候默认返回的是页面。如果需要返回json数据,需实现AccessDeniedHandler接口重写handle方法
*/
@Component
@Slf4j
public class AccessDeniedAuthenticationHandler implements AccessDeniedHandler {
private final ObjectMapper objectMapper;

public AccessDeniedAuthenticationHandler(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}

@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
AccessDeniedException e) throws IOException, ServletException {
log.info("进入AccessDeniedAuthenticationHandler");
log.info("无访问权限");
httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString("无访问权限"));
}
}

/**
* 未认证时会通过此逻辑处理
*/
@Component
@Slf4j
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

private final ObjectMapper objectMapper;

public CustomAuthenticationEntryPoint(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
log.info("进入CustomAuthenticationEntryPoint");
response.setCharacterEncoding("utf-8");
response.setContentType("text/javascript;charset=utf-8");
// 不携带token时(http://localhost:8083/java/f1), 返回错误信息:"Full authentication is required to access this resource"
response.getWriter().print(objectMapper.writeValueAsString(authException.getMessage()));
}
}

八、jwt配置类
/**
* JWT过滤器
* <p>
* OncePerRequestFilter,顾名思义,
* 它能够确保在一次请求中只通过一次filter,而需要重复的执行。
*/
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

private static final String HEADER_NAME = "Authorization";

public static final String TOKEN_PREFIX = "Bearer ";

@Autowired
private JwtTokenUtil jwtTokenUtil;

@Autowired
private SecurityProperties securityProperties;

@Autowired
private UserService userService;

private AntPathMatcher antPathMatcher = new AntPathMatcher();

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
System.out.println("进入JwtAuthenticationTokenFilter");
log.info("请求路径:{},请求方式为:{}", request.getRequestURI(), request.getMethod());
// 获取请求头Authorization
String authHeader = request.getHeader(HEADER_NAME);
if (StringUtils.isBlank(authHeader) || !authHeader.startsWith(TOKEN_PREFIX)) {
log.error("Authorization的开头不是Bearer,Authorization===>{}", authHeader);
// 前后端分离项目提示无权限信息
//responseEntity(response, HttpStatus.UNAUTHORIZED.value(), "暂无权限!");
filterChain.doFilter(request, response);
return;
}
// 截取token
String authToken = authHeader.substring(TOKEN_PREFIX.length());
//判断token是否失效
if (jwtTokenUtil.isTokenExpired(authToken)) {
log.info("token已过期!");
responseEntity(response, HttpStatus.UNAUTHORIZED.value(), "token已过期!");
return;
}
String randomKey = jwtTokenUtil.getMd5KeyFromToken(authToken);
String username = jwtTokenUtil.getUsernameFromToken(authToken);
//如果访问的是刷新Token的请求
if (antPathMatcher.match("/refreshToken", request.getRequestURI()) && Objects.equals(
RequestMethod.POST.toString(), request.getMethod())) {
final String getRandomKey = jwtTokenUtil.getRandomKey();
refreshEntity(response, HttpStatus.OK.value(), jwtTokenUtil.generateToken(username, getRandomKey),
jwtTokenUtil.refreshToken(authToken, jwtTokenUtil.getRandomKey()));
return;
}
/*
* 验证token是否合法
*/
if (StringUtils.isBlank(username) || StringUtils.isBlank(randomKey)) {
log.info("username{}或randomKey{} 可能为null!", username, randomKey);
responseEntity(response, HttpStatus.UNAUTHORIZED.value(), "暂无权限!");
return;
}
//获得用户名信息放入上下文中
if (SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,
null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
// token过期时间
long tokenExpireTime = jwtTokenUtil.getExpirationDateFromToken(authToken).getTime();

// token还剩余多少时间过期
long surplusExpireTime = (tokenExpireTime - System.currentTimeMillis()) / 1000;
log.info("Token剩余时间:" + surplusExpireTime);

filterChain.doFilter(request, response);

}

private void responseEntity(HttpServletResponse response, Integer status, String message) {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(status);
ModelMap modelMap = new ModelMap(String.valueOf(status), message);
try {
response.getWriter().write(JSON.toJSONString(modelMap));
} catch (IOException e) {
e.printStackTrace();
}
}

private void refreshEntity(HttpServletResponse response, Integer status, String token, String refreshToken) {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(status);
ModelMap modelMap = new ModelMap();
modelMap.put("token", JwtAuthenticationTokenFilter.TOKEN_PREFIX + token);
modelMap.put("refreshToken", JwtAuthenticationTokenFilter.TOKEN_PREFIX + refreshToken);
try {
response.getWriter().write(JSON.toJSONString(modelMap));
} catch (IOException e) {
e.printStackTrace();
}
}
}

@Data
public class JwtProperties {
/**
* 默认前面秘钥
*/
private String secret = "defaultSecret";

/**
* token默认有效期时长,1小时
*/
private Long expiration = 3600L;
/**
* token默认有效期时长,1个半小时
*/
private Long refreshExpiration = 5400L;

/**
* token的唯一标记
*/
private String md5Key = "randomKey";

/**
* GET请求是否需要进行Authentication请求头校验,true:默认校验;false:不拦截GET请求
*/
private boolean preventsGetMethod = true;
}


@Component
public class JwtTokenUtil {

@Autowired
private SecurityProperties securityProperties;

/**
* 获取用户名从token中
*/
public String getUsernameFromToken(String token) {
return getClaimFromToken(token).getSubject();
}

/**
* 获取jwt失效时间
*/
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token).getExpiration();
}

/**
* 获取私有的jwt claim
*/
public String getPrivateClaimFromToken(String token, String key) {
return getClaimFromToken(token).get(key).toString();
}

/**
* 获取md5 key从token中
*/
public String getMd5KeyFromToken(String token) {
return getPrivateClaimFromToken(token, securityProperties.getJwt().getMd5Key());
}

/**
* 获取jwt的payload部分
*/
public Claims getClaimFromToken(String token) {
return Jwts.parser().setSigningKey(generalKey()).parseClaimsJws(token).getBody();
}

/**
* <pre>
* 验证token是否失效
* true:过期 false:没过期
* </pre>
*/
public Boolean isTokenExpired(String token) {
try {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
} catch (ExpiredJwtException expiredJwtException) {
return true;
}
}

/**
* 生成token(通过用户名和签名时候用的随机数)
*/
public String generateToken(String userName, String randomKey) {
Map<String, Object> claims = new HashMap<>();
claims.put(securityProperties.getJwt().getMd5Key(), randomKey);
return doGenerateToken(claims, userName);
}

/**
* 生成token(通过用户名和签名时候用的随机数)
*/
public String generateRefreshToken(String userName, String randomKey) {
Map<String, Object> claims = new HashMap<>();
claims.put(securityProperties.getJwt().getMd5Key(), randomKey);
return doGenerateRefreshToken(claims, userName);
}

/**
* 由字符串生成加密key
*
* @return
*/
public SecretKey generalKey() {
byte[] encodedKey = Base64.encodeBase64(securityProperties.getJwt().getSecret().getBytes());
return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
}

/**
* 生成token
*/
private String doGenerateToken(Map<String, Object> claims, String subject) {
final Date createdDate = new Date();
final Date expirationDate = new Date(
createdDate.getTime() + securityProperties.getJwt().getExpiration() * 1000);

return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, generalKey())
.compact();
}

/**
* 生成token
*/
private String doGenerateRefreshToken(Map<String, Object> claims, String subject) {
final Date createdDate = new Date();
final Date expirationDate = new Date(
createdDate.getTime() + securityProperties.getJwt().getRefreshExpiration() * 1000);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, generalKey())
.compact();
}

/**
* 获取混淆MD5签名用的随机字符串
*/
public String getRandomKey() {
return getRandomString(6);
}

/**
* 获取随机位数的字符串
*/
public String getRandomString(int length) {
final String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}

/**
* 刷新token
*
* @param token:token
* @return
*/
public String refreshToken(String token, String randomKey) {
String refreshedToken;
try {
final Claims claims = getClaimFromToken(token);
refreshedToken = generateToken(claims.getSubject(), randomKey);
} catch (Exception e1) {
refreshedToken = null;
}
return refreshedToken;
}
}

@Component
@Data
@ConfigurationProperties(prefix = "security.jwt")
public class SecurityProperties {
private JwtProperties jwt = new JwtProperties();
}

九、UserDetailsService 主要用来用户认证授权
@Service
public class UserService implements UserDetailsService {

@Autowired
private PasswordEncoder passwordEncoder;

@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
System.out.println("进入UserService");
SysUser user = InitData.SYS_USERS.stream()
.filter(o -> StringUtils.equals(o.getUsername(), s))
.findFirst()
.orElse(null);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
if (!user.isEnabled()) {
throw new DisabledException("该账户已被禁用,请联系管理员");
} else if (!user.isAccountNonLocked()) {
throw new LockedException("该账号已被锁定");
} else if (!user.isAccountNonExpired()) {
throw new AccountExpiredException("该账号已过期,请联系管理员");
} else if (!user.isCredentialsNonExpired()) {
throw new CredentialsExpiredException("该账户的登录凭证已过期,请重新登录");
}
/**
* 1.为了捕获BadCredentialsException异常并自定义异常信息,否则返回默认的英文异常信息,也可以在自定义的provider捕获,
* 此处使用默认的provider{@link org.springframework.security.authentication.dao.DaoAuthenticationProvider}
* 2.remember-me时认证无法获取rawPassword(null),String rawPassword = request.getParameter("password");
* 认证未结束,此处获取的Authentication为空
* Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
* String rawPassword = (String) authentication.getCredentials();
*/

HttpServletRequest request
= ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String rawPassword = request.getParameter("password");
if (StringUtils.isNotEmpty(rawPassword)) {
//验证密码
if (!passwordEncoder.matches(rawPassword, user.getPassword())) {
throw new BadCredentialsException("输入密码错误!");
}
}
//把角色放入认证器里
List<GrantedAuthority> authorities = new ArrayList<>();
List<SysRole> roles = user.getRoles();
for (SysRole role : roles) {
//此处可以让SysRole实现GrantedAuthority接口并重写getAuthority方法
authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
}
user.setAuthorities(authorities);
//此处的密码是从数据库取出的密码,加密过的
//return new User(user.getUserName(), user.getPassword(), authorities);
return user;
}
}

十、加密
/**
* MD5加密工具
*/
public class MD5Util {

private static final String SALT = "jintianyoudianshuai";

public static String encode(String password) {
password = password + SALT;
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (Exception e) {
throw new RuntimeException(e);
}
char[] charArray = password.toCharArray();
byte[] byteArray = new byte[charArray.length];

for (int i = 0; i < charArray.length; i++) {
byteArray[i] = (byte) charArray[i];
}
byte[] md5Bytes = md5.digest(byteArray);
StringBuffer hexValue = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16) {
hexValue.append("0");
}

hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}

public static void main(String[] args) {
System.out.println(MD5Util.encode("123456"));
}
}

十一、controller访问
@Slf4j
//@ApiSort(3)
@ApiSupport(order = 3)
@Api(tags = "02-登录", value = "该参数没什么意义,在UI界面上也看到,所以不需要配置")
@RestController
public class LoginController {

@Resource
private AuthenticationManager authenticationManager;

@Autowired
private JwtTokenUtil jwtTokenUtil;

@ApiOperationSupport(order = 33)
@ApiOperation(value = "登录", notes = "")
@ApiImplicitParams( {
@ApiImplicitParam(name = "username", value = "用户名", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "password", value = "密码", required = true, dataType = "String", paramType = "query")
})
@PostMapping(value = "/toLogin", produces = "application/json; charset=utf-8")
public ResultData toLogin(
@RequestParam(value = "username", required = true) @NotBlank(message = "不能为空") String username,
@RequestParam(value = "password", required = true) @NotBlank(message = "不能为空") String password) {
try {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
= new UsernamePasswordAuthenticationToken(username, password);
Authentication authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);

final String randomKey = jwtTokenUtil.getRandomKey();
String uName = ((UserDetails) authentication.getPrincipal()).getUsername();
//生产JWT 令牌
final String token = jwtTokenUtil.generateToken(uName, randomKey);
final String refreshToken = jwtTokenUtil.generateRefreshToken(uName, randomKey);
ModelMap modelMap = new ModelMap();
modelMap.put("token", JwtAuthenticationTokenFilter.TOKEN_PREFIX + token);
modelMap.put("refreshToken", JwtAuthenticationTokenFilter.TOKEN_PREFIX + refreshToken);
log.info("【登录管理-登录】- success");
return ResultData.success("登录成功", modelMap);
} catch (Exception e) {
log.error("【登录管理-登录】- error, msg = {}", e.getMessage());
return ResultData.error(e.getMessage());
}
}

@ApiOperation(value = "刷新token", notes = "")
@PostMapping(value = "/refreshToken", produces = "application/json; charset=utf-8")
public void refreshToken(@RequestHeader(required = true) @NotBlank(message = "不能为空") String Authorization) {
try {
log.info("【登录管理-refreshToken】- success");
} catch (Exception e) {
log.error("【登录管理-refreshToken】- error, msg = {}", e.getMessage());
}
}
}


@Slf4j
//@ApiSort(1)
@ApiSupport(order = 1)
@Api(tags = "04-权限管理", value = "该参数没什么意义,在UI界面上也看到,所以不需要配置")
@RestController
public class PermissionController {

@PreAuthorize("hasAuthority('DOCKER')")
@GetMapping("/docker")
public String docker() {
return "说明你有docker权限";
}

@PreAuthorize("hasAuthority('JAVA')")
@GetMapping("/java")
public String java() {
return "说明你有java权限";
}

@PreAuthorize("hasAuthority('PHP')")
@GetMapping("/php")
public String php() {
return "说明你有最好语言的权限";
}

@GetMapping("/query")
public String query() {
if (1 == 1) {
throw new BusinessException("业务异常", "xxx");
}
return "说明你有最好语言的权限";
}
}
十二、公共异常处理类
@Data
public class BusinessException extends RuntimeException {
private String msg;
private Object data;

public BusinessException(String msg, Object data) {
super(msg);
System.out.println("进入BusinessException");
this.data = data;
}
}

/**
* <p>Description: 全局异常处理</p>
*/
@RestControllerAdvice
public class ServiceExceptionHandler {

/**
* 业务异常处理
* @param ex
* @return
*/
@ExceptionHandler(BusinessException.class)
public ResultData handleBusinessException(BusinessException ex) {
return ResultData.error(StringUtils.isNotBlank(ex.getMessage()) ? ex.getMessage() : "操作异常", ex.getData());
}


@ExceptionHandler(NoHandlerFoundException.class)
public String noHandlerFoundHandler(HttpServletRequest req, Exception ex) {
System.out.println("进入ServiceExceptionHandler");
return "msg:" + ex.getMessage();
}

@ExceptionHandler(Exception.class)
public ResultData handleException(Exception ex){
return ResultData.error("其他错误", ex);
}

}

前后不分离token处理类 (分离项目则无用)
@Slf4j
@Component
public class AppAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

@Autowired
private JwtTokenUtil jwtTokenUtil;

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
log.info("进入AppAuthenticationSuccessHandler");
final String randomKey = jwtTokenUtil.getRandomKey();
String username = ((UserDetails) authentication.getPrincipal()).getUsername();
log.info("username:{}", username);
//生产JWT 令牌
final String token = jwtTokenUtil.generateToken(username, randomKey);
final String refreshToken = jwtTokenUtil.generateRefreshToken(username, randomKey);
log.info("登录成功!");
ModelMap modelMap = new ModelMap(String.valueOf(HttpStatus.OK.value()), "登陆成功");
modelMap.put("token", JwtAuthenticationTokenFilter.TOKEN_PREFIX + token);
modelMap.put("refreshToken", JwtAuthenticationTokenFilter.TOKEN_PREFIX + refreshToken);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSON.toJSONString(modelMap));
}
}



posted @ 2021-08-05 14:45  一介青衣  阅读(326)  评论(0编辑  收藏  举报