一.前言吐槽
剛開始學習,網上找了很多例子,可能我比較小白,踩了很多坑,不知道爲什麼有些大佬,都不屑於測試下的,直接丟倉庫了,下載很多都是跑不起來的。看得我一臉懵逼的。好啦不吐槽了,記錄下自己學習的東東,開始自己的表演!
二.代碼
**2.1源碼地址:**https://github.com/myjsonman/spring-security-kavy.git
2.2代碼結構目錄:
2.3 在IDEA 創建一個簡單的Maven 工程添加pom對應的依賴
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<!-- 加密的 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mybatisplus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
<version>2.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.noggit</groupId>
<artifactId>noggit</artifactId>
<version>0.6</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
核心依賴:
security:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
JWt:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2.4 簡單的表設計:
設計有點粗超,見諒。
權限表:
CREATE TABLE `user_roles` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`role_id` int(11) DEFAULT NULL,
`role_str` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;
用戶表:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`user_img` varchar(255) DEFAULT NULL,
`updatetime` timestamp NULL DEFAULT NULL,
`status` char(1) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;
角色表:
CREATE TABLE `role` (
`id` int(50) NOT NULL AUTO_INCREMENT,
`role_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`updatetime` timestamp NULL DEFAULT NULL,
`role_desc` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
在這裏寫下項目的流程:
1.創建一個springboot項目
2.導入springSecurity和jwt 依賴
3.創建實體類
4.創建service層
5.創建mapper層
6.創建UserDetail去實現UserDetails
7.創建JwtUserDetailsServiceImpl去實現UserDetailsService
8.創建用戶登錄獲取權限的攔截器
9.配置config 處理攔截器
10.jwt的工具類生成token
開始Spring boot的一個簡單API
2.5 實體
@Data
public class Role {
private Integer id;
private String roleName;
private Timestamp updatetime;
private String roleDesc;
}
@Data
public class User implements Serializable {
private Integer id;
@NotBlank(message = "用戶名不能爲空")
@Size(min=5, max=20,message = "用戶名不能小於5位,大於20位")
private String username;
@NotBlank(message = "密碼不能爲空")
@Size(min=6,max = 20,message = "密碼不能小於6位,大於20位")
private String password;
private String userImg;
private Date updatetime;
private String status;
}
@Data
public class UserRoles {
private Integer id;
private Integer userId;
private Integer roleId;
private String roleStr;
}
2.6 service:
public interface UserService {
/**
* 註冊用戶
* @return
*/
void register(User user,String str);
/**
* 登陸
* @param username
* @param password
* @return
*/
ResponseUserToken login(String username, String password);
}
2.7 實現類
package com.kavy.springsecuritydemo.service.serviceImp;
import com.kavy.springsecuritydemo.entity.User;
import com.kavy.springsecuritydemo.entity.UserDetail;
import com.kavy.springsecuritydemo.entity.UserRoles;
import com.kavy.springsecuritydemo.exception.CustomException;
import com.kavy.springsecuritydemo.mapper.UserMapper;
import com.kavy.springsecuritydemo.mapper.UserRolesMapper;
import com.kavy.springsecuritydemo.result.ResultCode;
import com.kavy.springsecuritydemo.result.ResultJson;
import com.kavy.springsecuritydemo.service.UserService;
import com.kavy.springsecuritydemo.utils.JwtUtils;
import com.kavy.springsecuritydemo.utils.ResponseUserToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.apache.commons.lang.StringUtils;
import java.util.Date;
@Service
public class UserServiceImp implements UserService {
@Autowired
UserMapper userMapper;
@Autowired
private UserRolesMapper userRolesMapper;
@Autowired
private JwtUtils jwtUtils;
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void register(User user,String str) {
//查詢用戶
User oldUser = userMapper.findByUsername(user.getUsername());
if (oldUser != null) {
throw new CustomException(ResultJson.failure(ResultCode.BAD_REQUEST, "用戶已存在"));
}
//加密
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
user.setPassword(encoder.encode(user.getPassword()));
user.setUpdatetime(new Date(System.currentTimeMillis()));
user.setStatus("0");
userMapper.insert(user);
if (StringUtils.isNotBlank(str)){
//權限插入
String[] roles = str.split(",");
for (String role : roles) {
//如果原先有綁定權限就刪除
// userRolesMapper.deleteById(user.getId());
UserRoles userRoles = new UserRoles();
userRoles.setUserId(user.getId());
userRoles.setRoleId(Integer.parseInt(role));
userRoles.setRoleStr(str);
userRolesMapper.insert(userRoles);
}
}
}
@Override
public ResponseUserToken login(String username, String password) {
//用戶驗證
final Authentication authentication = authenticate(username, password);
//存儲認證信息
SecurityContextHolder.getContext().setAuthentication(authentication);
//生成token ,查看源代碼會發現調用getPrincipal()方法會返回一個實現了`UserDetails`接口的對象
final UserDetail userDetail = (UserDetail) authentication.getPrincipal();
//通過工具類生成token
final String token = "Bearer "+jwtUtils.generateAccessToken(userDetail);
//存儲token
jwtUtils.putToken(username, token);
// 學習 測試用,把用戶的信息也返回了
return new ResponseUserToken(token, userDetail);
}
private Authentication authenticate(String username, String password) {
try {
//該方法會去調用userDetailsService.loadUserByUsername()去驗證用戶名和密碼,如果正確,則存儲該用戶名密碼到“security 的 context中”
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException | BadCredentialsException e) {
throw new CustomException(ResultJson.failure(ResultCode.LOGIN_ERROR, e.getMessage()));
}
}
}
2.8 Mapper
注:說是MybatisPlus 其實就是保存數據的時候用了,不過我已經整合好了,大家Mybatis 和Plus 兩個可以無縫切換使用 。 BaseMapper<> 這個是MybatisPlus 的寫法;
RoleMapper:
package com.kavy.springsecuritydemo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.kavy.springsecuritydemo.entity.Role;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface RoleMapper extends BaseMapper<Role> {
/**
* 創建用戶角色
* @param userId
* @param roleId
* @return
*/
int insertRole(long userId, long roleId);
/**
* 根據角色id查找角色
* @param roleId
* @return
*/
Role findRoleById(long roleId);
/**
* 根據用戶id查找該用戶角色
* @param userId
* @return
*/
List<Role> findRoleByUserId(@Param("userId") Integer userId);
}
UserMapper:
@Mapper
public interface UserMapper extends BaseMapper<User> {
User findByUsername(String username);
}
UserRolesMapper:
public interface UserRolesMapper extends BaseMapper<UserRoles> {
}
roleMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kavy.springsecuritydemo.mapper.RoleMapper">
<insert id="insertRole">
insert into user_roles (user_id, role_id) VALUES (#{userId}, #{roleId});
</insert>
<select id="findRoleById" resultType="Role">
select id, role_name, role_desc from role where id = #{roleId}
</select>
<select id="findByUsername" parameterType="String" resultType="User">
SELECT id, username, password from user where username = #{username};
</select>
<select id="findRoleByUserId" resultType="Role">
select * from role where id in (SELECT role_id from user_roles where user_id = #{userId});
</select>
</mapper>
userMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kavy.springsecuritydemo.mapper.UserMapper">
<insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
insert into user (username, password,updatetime,status) VALUES (#{username}, #{password},#{updatetime},#{status});
</insert>
<select id="findByUsername" parameterType="String" resultType="User">
SELECT id, username, password from user where username = #{username};
</select>
<select id="queryByUsername" parameterType="String" resultType="User">
SELECT id, username, password from user where username = #{username};
</select>
</mapper>
Controller:
package com.kavy.springsecuritydemo.controller;
import com.kavy.springsecuritydemo.entity.User;
import com.kavy.springsecuritydemo.result.ResultCode;
import com.kavy.springsecuritydemo.result.ResultJson;
import com.kavy.springsecuritydemo.service.UserService;
import com.kavy.springsecuritydemo.utils.ResponseUserToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class UserController {
@Value("${jwt.header}")
private String tokenHeader;
@Autowired
UserService userService;
@GetMapping("/hello")
public String hello(){
return "hi security!";
}
/**
* 註冊
* @param user
*/
@PostMapping("/register")
public ResultJson signUp( User user ,String str) {
if (user==null){
ResultJson.failure(ResultCode.BAD_REQUEST);
}
userService.register(user,str);
return ResultJson.success();
}
/**
* 獲取token
* @param user
* @return
*/
@PostMapping("/login")
public ResultJson<ResponseUserToken> login(@RequestBody User user) {
final ResponseUserToken response = userService.login(user.getUsername(), user.getPassword());
return ResultJson.ok(response);
}
}
標題黨
臥槽 發現貼得有點囉嗦,Security 和JWT呢。別急大佬,別噴,馬上=。=上代碼
網咯配圖(咱也畫不出來,也不敢問,都是圍觀大佬的):
1.開始整合JWT認證
創建一個JwtUserDetailsServiceImpl 去實現 UserDetailsService接口,UserDetailsService接口就一個方法 loadUserByUsername()
package com.kavy.springsecuritydemo.service.serviceImp;
import com.kavy.springsecuritydemo.entity.Role;
import com.kavy.springsecuritydemo.entity.User;
import com.kavy.springsecuritydemo.entity.UserDetail;
import com.kavy.springsecuritydemo.mapper.RoleMapper;
import com.kavy.springsecuritydemo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.List;
@Configuration
public class JwtUserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(String.format("No userDetail found with username '%s'.", username));
}
//查詢權限封裝
List<Role> roleByUserId = roleMapper.findRoleByUserId(user.getId());
return new UserDetail(user.getUsername(),roleByUserId,user.getPassword());
}
}
再創建UserDetail 去實現UserDetails 接口,JWT通過這個接口去獲取用戶密碼和權限
package com.kavy.springsecuritydemo.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
@Data
public class UserDetail implements UserDetails {
private long id;
private String username;
private String password;
private List<Role> roles;
//private Collection<? extends GrantedAuthority> authorities;
public UserDetail(
String username,
List<Role> roles,
String password) {
this.username = username;
this.password = password;
this.roles = roles;
}
//getAuthorities獲取用戶包含的權限,返回權限集合,權限是一個繼承了GrantedAuthority的對象;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 根據自定義邏輯來返回用戶權限,如果用戶權限返回空或者和攔截路徑對應權限不同,驗證不通過
if (!roles.isEmpty()){
List<GrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
}
return authorities;
}
return null;
}
//getPassword和getUsername用於獲取密碼和用戶名;
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
//isAccountNonExpired方法返回boolean類型,用於判斷賬戶是否未過期,未過期返回true反之返回false;
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
//isAccountNonLocked方法用於判斷賬戶是否未鎖定;
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
//isCredentialsNonExpired用於判斷用戶憑證是否沒過期,即密碼是否未過期;
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//isEnabled方法用於判斷用戶是否可用。
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
}
攔截器filter
package com.kavy.springsecuritydemo.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.kavy.springsecuritydemo.service.serviceImp.JwtUserDetailsServiceImpl;
import com.kavy.springsecuritydemo.utils.JwtUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import io.jsonwebtoken.ExpiredJwtException;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtUserDetailsServiceImpl jwtUserDetailsService;
@Autowired
private JwtUtils jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
//獲取header 的Authorization
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
// JWT報文表頭的格式是"Bearer token". 去除"Bearer ",直接獲取token
// only the Token
if (StringUtils.isNotEmpty(requestTokenHeader) && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
//獲取username
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
logger.warn("JWT Token does not begin with Bearer String");
}
// Once we get the token validate it.
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//獲取 userDetails
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
// if token is valid configure Spring Security to manually set
// authentication
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
錯誤返回:
package com.kavy.springsecuritydemo.filter;
import java.io.IOException;
import java.io.Serializable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
/**
* 用來解決匿名用戶訪問無權限資源時的異常
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = 1L;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
核心配置config:
package com.kavy.springsecuritydemo.config;
import com.kavy.springsecuritydemo.filter.JwtAuthenticationEntryPoint;
import com.kavy.springsecuritydemo.filter.JwtRequestFilter;
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.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @EnableWebSecurity註解繼承WebSecurityConfigurerAdapter的類,這樣就構成了Spring Security的配置。
*
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
private JwtRequestFilter jwtRequestFilter;
public WebSecurityConfig(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// We don't need CSRF for this example
httpSecurity.csrf().disable()
//用來解決匿名用戶訪問無權限資源時的異常
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
//禁用session 無狀態
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// dont authenticate this particular request
.and()
.authorizeRequests()
//註冊 register 和登錄 login 不需要驗證 就可以訪問,其他的都需要token驗證纔可以訪問
.antMatchers(HttpMethod.POST, "/api/login", "/api/register", "/error/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity
.headers()
.frameOptions().sameOrigin() // required to set for H2 else H2 Console will be blank.
.cacheControl();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
/* @Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() // 表單方式
// http.httpBasic() // HTTP Basic方式
.loginPage("/login.html") //.loginPage("/login.html")指定了跳轉到登錄頁面的請求URL
.loginProcessingUrl("/login") //.loginProcessingUrl("/login")對應登錄頁面form表單的action="/login"
.and()
.authorizeRequests() // 授權配置
.antMatchers("/login.html").permitAll() // 表示跳轉到登錄頁面的請求不被攔截,否則會進入無限循環。
.anyRequest() // 所有請求
.authenticated() // 都需要認證
.and().csrf().disable();
}*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
JWT的工具類:
用來生成token的
package com.kavy.springsecuritydemo.utils;
import com.kavy.springsecuritydemo.entity.UserDetail;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class JwtUtils {
private static final String CLAIM_KEY_USER_ID = "user_id";
private static final String CLAIM_KEY_AUTHORITIES = "scope";
private Map<String, String> tokenMap = new ConcurrentHashMap<>(32);
//密匙
@Value("${jwt.secret}")
private String secret;
//有效期
@Value("${jwt.expiration}")
private Long access_token_expiration;
//刷新密匙
@Value("${jwt.expiration}")
private Long refresh_token_expiration;
private final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
//獲取token
public String generateAccessToken(UserDetail userDetail) {
Map<String, Object> claims = generateClaims(userDetail);
claims.put(CLAIM_KEY_AUTHORITIES, authoritiesToArray(userDetail.getAuthorities()).get(0));
return generateAccessToken(userDetail.getUsername(), claims);
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
public void putToken(String userName, String token) {
tokenMap.put(userName, token);
}
/**
* 生成token的過期時間
* @return
*/
private Date generateExpirationDate(long expiration) {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
/**
* 從token中獲取claims
*
**/
public Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
/**
* 獲取用戶名
* @param token
* @return
*/
public String getUsernameFromToken(String token) {
String username;
try {
final Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 獲取token 過期時間
* @param token
* @return
*/
public Date getExpirationDateFromToken(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
expiration = claims.getExpiration();
} catch (Exception e) {
expiration = null;
}
return expiration;
}
/**
* 判斷token 是否過期
* @param token
* @return
*/
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
/**
* 判斷token是否可以刷新
* @param token
* @return
*/
public Boolean canTokenBeRefreshed(String token) {
final Date created = getCreatedDateFromToken(token);
return !isTokenExpired(token);
}
/**
* 從token中獲取創建時間
* @param token
* @return
*/
public Date getCreatedDateFromToken(String token) {
Date created;
try {
final Claims claims = getClaimsFromToken(token);
created = claims.getIssuedAt();
} catch (Exception e) {
created = null;
}
return created;
}
/**
* 刷新token
* @param token
* @return
*/
public String refreshToken(String token) {
String refreshedToken;
try {
final Claims claims = getClaimsFromToken(token);
refreshedToken = generateAccessToken(claims.getSubject(), claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}
/**
* claims
* @param userDetail
* @return
*/
private Map<String, Object> generateClaims(UserDetail userDetail) {
Map<String, Object> claims = new HashMap<>(16);
claims.put(CLAIM_KEY_USER_ID, userDetail.getId());
System.out.println("userDetail.getId()--------->>>"+userDetail.getId());
System.out.println("userDetail--->"+userDetail.toString());
return claims;
}
// 傳入 username 過期時間 claims 獲取token
private String generateAccessToken(String subject, Map<String, Object> claims) {
// subject --->userDetail.getUsername() access_token_expiration過期時間
return generateToken(subject, claims, access_token_expiration);
}
private List authoritiesToArray(Collection<? extends GrantedAuthority> authorities) {
List<String> list = new ArrayList<>();
for (GrantedAuthority ga : authorities) {
list.add(ga.getAuthority());
}
return list;
}
/**
* 生成token
* @param subject
* @param claims
* @param expiration
* @return
*/
private String generateToken(String subject, Map<String, Object> claims, long expiration) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject) //username 設置面向用戶
// .setId(UUID.randomUUID().toString())
.setIssuedAt(new Date()) //簽發時間
.setExpiration(generateExpirationDate(expiration)) //生成token的過期時間
.compressWith(CompressionCodecs.DEFLATE)
.signWith(SIGNATURE_ALGORITHM, secret) //SignatureAlgorithm.HS512, secret 生成簽名
.compact();
}
}
整個Security就完成了,在此我只做了註冊和獲取token的兩個,嗯,還有一些返回的封裝和錯誤碼返回處理,沒有貼出來,具體可以去看下源碼,其他的可以根據自己需求寫了;
測試:
沒有獲取到token,所以會報錯
註冊:
註冊成功,通過註冊的用戶和密碼獲取token
通過獲取到的token,再次訪問hello接口