1.demo-admin模塊引入依賴
<!-- spring security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2.編寫Spring Security認(rèn)證配置類
package com.lvxk.demo.admin.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
/**
* WebSecurityConfig
* Description: <br/>
* date: 2020/5/5 15:36<br/>
*
* @author lvxk<br />
* @since JDK 1.8
*/
@Configuration
@EnableWebSecurity // 開啟Spring Security
@EnableGlobalMethodSecurity(prePostEnabled = true) // 開啟權(quán)限注解,如:@PreAuthorize注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// 使用自定義身份驗(yàn)證組件
auth.authenticationProvider(new JwtAuthenticationProvider(userDetailsService));
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 禁用 csrf, 由于使用的是JWT肺孵,我們這里不需要csrf
http.cors().and().csrf().disable()
.authorizeRequests()
// 跨域預(yù)檢請(qǐng)求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// web jars
.antMatchers("/webjars/**").permitAll()
// 查看SQL監(jiān)控(druid)
.antMatchers("/druid/**").permitAll()
// 首頁和登錄頁面
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
// swagger
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/v2/api-docs").permitAll()
.antMatchers("/webjars/springfox-swagger-ui/**").permitAll()
// 驗(yàn)證碼
.antMatchers("/captcha/**").permitAll()
// 服務(wù)監(jiān)控
.antMatchers("/actuator/**").permitAll()
// 其他所有請(qǐng)求需要身份認(rèn)證
.anyRequest().authenticated();
// 退出登錄處理器
http.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());
// token驗(yàn)證過濾器
http.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
}
@Bean
@Override
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}
3.新建security包,編寫登錄認(rèn)證過濾器
package com.lvxk.demo.admin.security;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
/**
* JwtAuthenticationFilter
* Description: <br/>
* date: 2020/5/5 15:39<br/>
*
* @author lvxk<br />
* @since JDK 1.8
*/
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
@Autowired
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// 獲取token, 并檢查登錄狀態(tài)
SecurityUtils.checkAuthentication(request);
chain.doFilter(request, response);
}
}
4.創(chuàng)建util包和SecurityUtils
package com.lvxk.demo.admin.util;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
/**
* SecurityUtils
* Description: <br/>
* date: 2020/5/5 15:42<br/>
*
* @author lvxk<br />
* @since JDK 1.8
*/
public class SecurityUtils {
/**
* 系統(tǒng)登錄認(rèn)證
* @param request
* @param username
* @param password
* @param authenticationManager
* @return
*/
public static JwtAuthenticatioToken login(HttpServletRequest request, String username, String password, AuthenticationManager authenticationManager) {
JwtAuthenticatioToken token = new JwtAuthenticatioToken(username, password);
token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 執(zhí)行登錄認(rèn)證過程
Authentication authentication = authenticationManager.authenticate(token);
// 認(rèn)證成功存儲(chǔ)認(rèn)證信息到上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
// 生成令牌并返回給客戶端
token.setToken(JwtTokenUtils.generateToken(authentication));
return token;
}
/**
* 獲取令牌進(jìn)行認(rèn)證
* @param request
*/
public static void checkAuthentication(HttpServletRequest request) {
// 獲取令牌并根據(jù)令牌獲取登錄認(rèn)證信息
Authentication authentication = JwtTokenUtils.getAuthenticationeFromToken(request);
// 設(shè)置登錄認(rèn)證信息到上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
}
/**
* 獲取當(dāng)前用戶名
* @return
*/
public static String getUsername() {
String username = null;
Authentication authentication = getAuthentication();
if(authentication != null) {
Object principal = authentication.getPrincipal();
if(principal != null && principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername();
}
}
return username;
}
/**
* 獲取用戶名
* @return
*/
public static String getUsername(Authentication authentication) {
String username = null;
if(authentication != null) {
Object principal = authentication.getPrincipal();
if(principal != null && principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername();
}
}
return username;
}
/**
* 獲取當(dāng)前登錄信息
* @return
*/
public static Authentication getAuthentication() {
if(SecurityContextHolder.getContext() == null) {
return null;
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication;
}
}
5.JwtTokenUtils
package com.lvxk.demo.admin.util;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
/**
* JwtTokenUtils
* Description: <br/>
* date: 2020/5/5 15:45<br/>
*
* @author lvxk<br />
* @since JDK 1.8
*/
public class JwtTokenUtils implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用戶名稱
*/
private static final String USERNAME = Claims.SUBJECT;
/**
* 創(chuàng)建時(shí)間
*/
private static final String CREATED = "created";
/**
* 權(quán)限列表
*/
private static final String AUTHORITIES = "authorities";
/**
* 密鑰
*/
private static final String SECRET = "abcdefgh";
/**
* 有效期12小時(shí)
*/
private static final long EXPIRE_TIME = 12 * 60 * 60 * 1000;
/**
* 生成令牌
*
* @param authentication 用戶
* @return 令牌
*/
public static String generateToken(Authentication authentication) {
Map<String, Object> claims = new HashMap<>(3);
claims.put(USERNAME, SecurityUtils.getUsername(authentication));
claims.put(CREATED, new Date());
claims.put(AUTHORITIES, authentication.getAuthorities());
return generateToken(claims);
}
/**
* 從數(shù)據(jù)聲明生成令牌
*
* @param claims 數(shù)據(jù)聲明
* @return 令牌
*/
private static String generateToken(Map<String, Object> claims) {
Date expirationDate = new Date(System.currentTimeMillis() + EXPIRE_TIME);
return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, SECRET).compact();
}
/**
* 從令牌中獲取用戶名
*
* @param token 令牌
* @return 用戶名
*/
public static String getUsernameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 根據(jù)請(qǐng)求令牌獲取登錄認(rèn)證信息
* @param request 令牌
* @return 用戶名
*/
public static Authentication getAuthenticationeFromToken(HttpServletRequest request) {
Authentication authentication = null;
// 獲取請(qǐng)求攜帶的令牌
String token = JwtTokenUtils.getToken(request);
if(token != null) {
// 請(qǐng)求令牌不能為空
if(SecurityUtils.getAuthentication() == null) {
// 上下文中Authentication為空
Claims claims = getClaimsFromToken(token);
if(claims == null) {
return null;
}
String username = claims.getSubject();
if(username == null) {
return null;
}
if(isTokenExpired(token)) {
return null;
}
Object authors = claims.get(AUTHORITIES);
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
if (authors != null && authors instanceof List) {
for (Object object : (List) authors) {
authorities.add(new GrantedAuthorityImpl((String) ((Map) object).get("authority")));
}
}
authentication = new JwtAuthenticatioToken(username, null, authorities, token);
} else {
if(validateToken(token, SecurityUtils.getUsername())) {
// 如果上下文中Authentication非空,且請(qǐng)求令牌合法厨喂,直接返回當(dāng)前登錄認(rèn)證信息
authentication = SecurityUtils.getAuthentication();
}
}
}
return authentication;
}
/**
* 從令牌中獲取數(shù)據(jù)聲明
*
* @param token 令牌
* @return 數(shù)據(jù)聲明
*/
private static Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
/**
* 驗(yàn)證令牌
* @param token
* @param username
* @return
*/
public static Boolean validateToken(String token, String username) {
String userName = getUsernameFromToken(token);
return (userName.equals(username) && !isTokenExpired(token));
}
/**
* 刷新令牌
* @param token
* @return
*/
public static String refreshToken(String token) {
String refreshedToken;
try {
Claims claims = getClaimsFromToken(token);
claims.put(CREATED, new Date());
refreshedToken = generateToken(claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}
/**
* 判斷令牌是否過期
*
* @param token 令牌
* @return 是否過期
*/
public static Boolean isTokenExpired(String token) {
try {
Claims claims = getClaimsFromToken(token);
Date expiration = claims.getExpiration();
return expiration.before(new Date());
} catch (Exception e) {
return false;
}
}
/**
* 獲取請(qǐng)求token
* @param request
* @return
*/
public static String getToken(HttpServletRequest request) {
String token = request.getHeader("Authorization");
String tokenHead = "Bearer ";
if(token == null) {
token = request.getHeader("token");
} else if(token.contains(tokenHead)){
token = token.substring(tokenHead.length());
}
if("".equals(token)) {
token = null;
}
return token;
}
}
6.JwtAuthenticationProvider
package com.lvxk.demo.admin.security;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* JwtAuthenticationProvider
* Description: <br/>
* date: 2020/5/5 15:57<br/>
*
* @author lvxk<br />
* @since JDK 1.8
*/
public class JwtAuthenticationProvider extends DaoAuthenticationProvider {
public JwtAuthenticationProvider(UserDetailsService userDetailsService) {
setUserDetailsService(userDetailsService);
}
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
String salt = ((JwtUserDetails) userDetails).getSalt();
// 覆寫密碼驗(yàn)證邏輯
if (!new PasswordEncoder(salt).matches(userDetails.getPassword(), presentedPassword)) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
7.認(rèn)證信息查詢 security包下新建一個(gè)UserDetailsServiceImpl
package com.lvxk.demo.admin.security;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* 用戶登錄認(rèn)證信息查詢
* Description: <br/>
* date: 2020/5/5 15:59<br/>
*
* @author lvxk<br />
* @since JDK 1.8
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SysUserService sysUserService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user = sysUserService.findByName(username);
if (user == null) {
throw new UsernameNotFoundException("該用戶不存在");
}
// 用戶權(quán)限列表赶熟,根據(jù)用戶擁有的權(quán)限標(biāo)識(shí)與如 @PreAuthorize("hasAuthority('sys:menu:view')") 標(biāo)注的接口對(duì)比,決定是否可以調(diào)用接口
Set<String> permissions = sysUserService.findPermissions(user.getName());
List<GrantedAuthority> grantedAuthorities = permissions.stream().map(GrantedAuthorityImpl::new).collect(Collectors.toList());
return new JwtUserDetails(user.getName(), user.getPassword(), user.getSalt(), grantedAuthorities);
}
}
8.安全用戶模型JwtUserDetails
package com.lvxk.demo.admin.security;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* 安全用戶模型
* Description: <br/>
* date: 2020/5/5 16:05<br/>
*
* @author lvxk<br />
* @since JDK 1.8
*/
public class JwtUserDetails implements UserDetails {
private static final long serialVersionUID = 1L;
private String username;
private String password;
private String salt;
private Collection<? extends GrantedAuthority> authorities;
JwtUserDetails(String username, String password, String salt, Collection<? extends GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.salt = salt;
this.authorities = authorities;
}
@Override
public String getUsername() {
return username;
}
@JsonIgnore
@Override
public String getPassword() {
return password;
}
public String getSalt() {
return salt;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
}