JWT 是什么
json web 令牌
Spring Boot 后端
jpa 实体类
package org.malred.learnjwtsecurity.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.Collection;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String username;
private String password;
@ManyToMany(fetch = FetchType.EAGER)
private Collection<Role> roles = new ArrayList<>();
}
package org.malred.learnjwtsecurity.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
}
操作数据库的接口
package org.malred.learnjwtsecurity.repo;
import org.malred.learnjwtsecurity.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepo extends JpaRepository<User, Long> {
User findByUsername(String username);
}
package org.malred.learnjwtsecurity.repo;
import org.malred.learnjwtsecurity.domain.Role;
import org.springframework.data.jpa.repository.JpaRepository;
public interface RoleRepo extends JpaRepository<Role, Long> {
Role findByName(String name);
}
service
package org.malred.learnjwtsecurity.service;
import org.malred.learnjwtsecurity.domain.Role;
import org.malred.learnjwtsecurity.domain.User;
import java.util.List;
public interface UserService {
User saveUser(User user);
Role saveRole(Role role);
void addRoleToUser(String username,String roleName);
User getUser(String username);
List<User> getUsers();
}
package org.malred.learnjwtsecurity.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.malred.learnjwtsecurity.domain.Role;
import org.malred.learnjwtsecurity.domain.User;
import org.malred.learnjwtsecurity.repo.RoleRepo;
import org.malred.learnjwtsecurity.repo.UserRepo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Slf4j
@Service
@Transactional
// 注入需要的依赖
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserRepo userRepo;
private final RoleRepo roleRepo;
@Override
public User saveUser(User user) {
return userRepo.save(user);
}
@Override
public Role saveRole(Role role) {
return roleRepo.save(role);
}
@Override
public void addRoleToUser(String username, String roleName) {
User user = userRepo.findByUsername(username);
Role role = roleRepo.findByName(roleName);
// 自动保存
user.getRoles().add(role);
}
@Override
public User getUser(String username) {
return userRepo.findByUsername(username);
}
@Override
public List<User> getUsers() {
return userRepo.findAll();
}
}
service log
package org.malred.learnjwtsecurity.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.malred.learnjwtsecurity.domain.Role;
import org.malred.learnjwtsecurity.domain.User;
import org.malred.learnjwtsecurity.repo.RoleRepo;
import org.malred.learnjwtsecurity.repo.UserRepo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Slf4j
@Service
@Transactional
// 注入需要的依赖
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserRepo userRepo;
private final RoleRepo roleRepo;
@Override
public User saveUser(User user) {
log.info("Saving new user {} to the database", user.getName());
return userRepo.save(user);
}
@Override
public Role saveRole(Role role) {
log.info("Saving new role {} to the database", role.getName());
return roleRepo.save(role);
}
@Override
public void addRoleToUser(String username, String roleName) {
log.info("Adding role {} to user {}", roleName, username);
User user = userRepo.findByUsername(username);
Role role = roleRepo.findByName(roleName);
// 自动保存
user.getRoles().add(role);
}
@Override
public User getUser(String username) {
log.info("Fetching user {}", username);
return userRepo.findByUsername(username);
}
@Override
public List<User> getUsers() {
log.info("Fetching users");
return userRepo.findAll();
}
}
controller
spring.datasource.url=jdbc:mysql://localhost:3307/jwt
spring.datasource.username=root
spring.datasource.password=123456
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
spring.jpa.properties.hibernate.format_sql=true
package org.malred.learnjwtsecurity.api;
import lombok.RequiredArgsConstructor;
import org.malred.learnjwtsecurity.domain.User;
import org.malred.learnjwtsecurity.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class UserResource {
private final UserService userService;
@GetMapping("/users")
public ResponseEntity<List<User>>getUsers(){
return ResponseEntity.ok().body(userService.getUsers());
}
}
controller over
package org.malred.learnjwtsecurity.api;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.malred.learnjwtsecurity.domain.Role;
import org.malred.learnjwtsecurity.domain.User;
import org.malred.learnjwtsecurity.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.net.URI;
import java.util.List;
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class UserResource {
private final UserService userService;
@GetMapping("/users")
public ResponseEntity<List<User>> getUsers() {
return ResponseEntity.ok().body(userService.getUsers());
}
@PostMapping("/user/save")
public ResponseEntity<User> saveUser(@RequestBody User user) {
/*
如果你想让你的响应状态为 201 Created,可以使用 ResponseEntity.created(URI) 方法。
这个方法会将响应状态设为 201 Created,并在响应头中设置一个名为 "Location" 的头部,其值为你提供的 URI。
*/
URI uri = URI.create(
ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/api/user/save").toUriString()
);
return ResponseEntity.created(uri).body(userService.saveUser(user));
}
@PostMapping("/role/save")
public ResponseEntity<Role> saveUser(@RequestBody Role role) {
URI uri = URI.create(
ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/api/role/save").toUriString()
);
return ResponseEntity.created(uri).body(userService.saveRole(role));
}
@PostMapping("/role/addtouser")
public ResponseEntity<?> addRoleToUser(@RequestBody RoleToUserForm form) {
userService.addRoleToUser(form.getUsername(), form.getRolename());
return ResponseEntity.ok().build();
}
}
@Data
class RoleToUserForm {
private String username;
private String rolename;
}
准备数据
package org.malred.learnjwtsecurity;
import org.malred.learnjwtsecurity.domain.Role;
import org.malred.learnjwtsecurity.domain.User;
import org.malred.learnjwtsecurity.service.UserService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.ArrayList;
@SpringBootApplication
public class LearnJwtSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(LearnJwtSecurityApplication.class, args);
}
@Bean
CommandLineRunner run(UserService userService) {
return args -> {
userService.saveUser(
new User(null, "John Travolta",
"john", "1234",
new ArrayList<>())
);
userService.saveUser(
new User(null, "Will Smith",
"will", "1234",
new ArrayList<>())
);
userService.saveUser(
new User(null, "Jim Carry",
"jim", "1234",
new ArrayList<>())
);
userService.saveUser(
new User(null, "Arnold Schwarzenegger",
"arnold", "1234",
new ArrayList<>())
);
userService.saveRole(new Role(null, "ROLE_USER"));
userService.saveRole(new Role(null, "ROLE_MANAGER"));
userService.saveRole(new Role(null, "ROLE_ADMIN"));
userService.saveRole(new Role(null, "ROLE_SUPER_ADMIN"));
userService.addRoleToUser("john", "ROLE_USER");
userService.addRoleToUser("john", "ROLE_MANAGER");
userService.addRoleToUser("will", "ROLE_MANAGER");
userService.addRoleToUser("jim", "ROLE_ADMIN");
userService.addRoleToUser("arnold", "ROLE_SUPER_ADMIN");
userService.addRoleToUser("arnold", "ROLE_ADMIN");
userService.addRoleToUser("arnold", "ROLE_USER");
};
}
}
配置 security 从数据库校验密码
package org.malred.learnjwtsecurity.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.malred.learnjwtsecurity.domain.Role;
import org.malred.learnjwtsecurity.domain.User;
import org.malred.learnjwtsecurity.repo.RoleRepo;
import org.malred.learnjwtsecurity.repo.UserRepo;
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 org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Slf4j
@Service
@Transactional
// 注入需要的依赖
@RequiredArgsConstructor
public class UserServiceImpl implements UserService, UserDetailsService {
private final UserRepo userRepo;
private final RoleRepo roleRepo;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepo.findByUsername(username);
if (user == null) {
log.error("User not found in the database");
throw new UsernameNotFoundException("User not found in the database");
} else {
log.info("User found in the database: {}", username);
}
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
user.getRoles().forEach(role -> {
authorities.add(
new SimpleGrantedAuthority(role.getName())
);
});
return new org.springframework.security.core.userdetails.User(
user.getUsername(), user.getPassword(), authorities
);
}
// ...
}
package org.malred.learnjwtsecurity;
import org.malred.learnjwtsecurity.domain.Role;
import org.malred.learnjwtsecurity.domain.User;
import org.malred.learnjwtsecurity.service.UserService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.ArrayList;
@SpringBootApplication
public class LearnJwtSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(LearnJwtSecurityApplication.class, args);
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
CommandLineRunner run(UserService userService) {
//...
}
}
package org.malred.learnjwtsecurity.security;
import lombok.RequiredArgsConstructor;
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.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.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
身份认证和权限认证
身份认证(authentication)是认证你是用户,可以访问系统
权限认证(authorization)是规定你可以使用的系统的功能
身份认证 filter
package org.malred.learnjwtsecurity.security;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.malred.learnjwtsecurity.filter.CustomAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.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.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(STATELESS);
http.authorizeRequests().anyRequest().permitAll();
http.addFilter(new CustomAuthenticationFilter(authenticationManagerBean()));
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
package org.malred.learnjwtsecurity.filter;
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.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;
@Slf4j
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
public CustomAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = request.getParameter("username");
String password = request.getParameter("password");
log.info("Username is: {}", username);
log.info("Password is: {}", password);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, password);
return authenticationManager.authenticate(authenticationToken);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
}
}
jwt
登录成功后返回 jwt token
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.1</version>
</dependency>
package org.malred.learnjwtsecurity.filter;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
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.core.userdetails.User;
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.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.stream.Collectors;
@Slf4j
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
public CustomAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = request.getParameter("username");
String password = request.getParameter("password");
log.info("Username is: {}", username);
log.info("Password is: {}", password);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, password);
return authenticationManager.authenticate(authenticationToken);
}
@Override
protected void successfulAuthentication(
HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authentication
) throws IOException, ServletException {
User user = (User) authentication.getPrincipal();
// 密钥加密
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
String access_token = JWT.create()
.withSubject(user.getUsername())
// 过期时间 一分钟
.withExpiresAt(new Date(System.currentTimeMillis() + 10 * 60 * 1000))
.withIssuer(request.getRequestURL().toString())
.withClaim("roles", user.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()))
.sign(algorithm);
String refresh_token = JWT.create()
.withSubject(user.getUsername())
// 过期时间 30分钟
.withExpiresAt(new Date(System.currentTimeMillis() + 30 * 60 * 1000))
.withIssuer(request.getRequestURL().toString())
.sign(algorithm);
response.setHeader("access_token", access_token);
response.setHeader("refresh_token", refresh_token);
}
}
密码加密,将 jwt 通过 json 返回
package org.malred.learnjwtsecurity.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.malred.learnjwtsecurity.domain.Role;
import org.malred.learnjwtsecurity.domain.User;
import org.malred.learnjwtsecurity.repo.RoleRepo;
import org.malred.learnjwtsecurity.repo.UserRepo;
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 org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Slf4j
@Service
@Transactional
// 注入需要的依赖
@RequiredArgsConstructor
public class UserServiceImpl implements UserService, UserDetailsService {
private final UserRepo userRepo;
private final RoleRepo roleRepo;
private final PasswordEncoder passwordEncoder;
@Override
public User saveUser(User user) {
log.info("Saving new user {} to the database", user.getName());
// 密码加密
user.setPassword(passwordEncoder.encode(user.getPassword()));
return userRepo.save(user);
}
// ...
}
package org.malred.learnjwtsecurity.filter;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.fasterxml.jackson.databind.ObjectMapper;
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.core.userdetails.User;
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.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@Slf4j
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
public CustomAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// ...
}
@Override
protected void successfulAuthentication(
HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authentication
) throws IOException, ServletException {
User user = (User) authentication.getPrincipal();
// 密钥加密
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
String access_token = JWT.create()
.withSubject(user.getUsername())
// 过期时间 一分钟
.withExpiresAt(new Date(System.currentTimeMillis() + 10 * 60 * 1000))
.withIssuer(request.getRequestURL().toString())
.withClaim("roles", user.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()))
.sign(algorithm);
String refresh_token = JWT.create()
.withSubject(user.getUsername())
// 过期时间 30分钟
.withExpiresAt(new Date(System.currentTimeMillis() + 30 * 60 * 1000))
.withIssuer(request.getRequestURL().toString())
.sign(algorithm);
// 在响应头返回
// response.setHeader("access_token", access_token);
// response.setHeader("refresh_token", refresh_token);
Map<String, String> tokens = new HashMap<>();
tokens.put("access_token", access_token);
tokens.put("refresh_token", refresh_token);
response.setContentType(APPLICATION_JSON_VALUE);
// 用json格式返回
new ObjectMapper().writeValue(response.getOutputStream(), tokens);
}
}
package org.malred.learnjwtsecurity.security;
import lombok.RequiredArgsConstructor;
import org.malred.learnjwtsecurity.filter.CustomAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.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.crypto.password.PasswordEncoder;
import static org.springframework.http.HttpMethod.POST;
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
@Override
protected void configure(HttpSecurity http) throws Exception {
CustomAuthenticationFilter customAuthenticationFilter =
new CustomAuthenticationFilter(authenticationManagerBean());
// 登录接口改为/api/login
customAuthenticationFilter.setFilterProcessesUrl("/api/login");
// 关闭csrf
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(STATELESS);
http.authorizeRequests().antMatchers("/api/login/**").permitAll();
http.authorizeRequests().antMatchers(GET, "/api/user/**")
.hasAnyAuthority("ROLE_USER");
http.authorizeRequests().antMatchers(POST, "/api/user/save/**")
.hasAnyAuthority("ROLE_ADMIN");
// authenticated: 需要认证过的才能访问
// permitAll: 无需权限即可访问
http.authorizeRequests().anyRequest().authenticated();
// http.addFilter(new CustomAuthenticationFilter(authenticationManagerBean()));
http.addFilter(customAuthenticationFilter);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
权限认证过滤器
package org.malred.learnjwtsecurity.filter;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import static java.util.Arrays.stream;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
public class CustomAuthorizationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request.getServletPath().equals("/api/login")) {
// 不做操作,传递到下一个过滤器
filterChain.doFilter(request, response);
} else {
String authorizationHeader = request.getHeader(AUTHORIZATION);
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
try {
// 获取携带的token
String token = authorizationHeader.substring("Bearer ".length());
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
JWTVerifier verifier = JWT.require(algorithm).build();
// 解码jwt并取数据
DecodedJWT decodedJWT = verifier.verify(token);
String username = decodedJWT.getSubject();
String[] roles = decodedJWT.getClaim("roles").asArray(String.class);
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
// roles转为SimpleGrantedAuthority对象
stream(roles).forEach(role -> {
authorities.add(new SimpleGrantedAuthority(role));
});
// 认证信息存入上下文
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
} catch (Exception e) {
}
} else {
filterChain.doFilter(request, response);
}
}
}
}
权限认证过滤器 错误处理
package org.malred.learnjwtsecurity.filter;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import static java.util.Arrays.stream;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@Slf4j
public class CustomAuthorizationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request.getServletPath().equals("/api/login")) {
// 不做操作,传递到下一个过滤器
filterChain.doFilter(request, response);
} else {
String authorizationHeader = request.getHeader(AUTHORIZATION);
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
try {
// 获取携带的token
String token = authorizationHeader.substring("Bearer ".length());
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
JWTVerifier verifier = JWT.require(algorithm).build();
// 解码jwt并取数据
DecodedJWT decodedJWT = verifier.verify(token);
String username = decodedJWT.getSubject();
String[] roles = decodedJWT.getClaim("roles").asArray(String.class);
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
// roles转为SimpleGrantedAuthority对象
stream(roles).forEach(role -> {
authorities.add(new SimpleGrantedAuthority(role));
});
// 认证信息存入上下文
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
} catch (Exception e) {
log.error("Error loggin in: {}", e.getMessage());
response.setHeader("error", e.getMessage());
// response.sendError(FORBIDDEN.value()); // 403
response.setStatus(FORBIDDEN.value()); // 403
Map<String, String> errors = new HashMap<>();
errors.put("error_message", e.getMessage());
response.setContentType(APPLICATION_JSON_VALUE);
// 用json格式返回
new ObjectMapper().writeValue(response.getOutputStream(), errors);
}
} else {
filterChain.doFilter(request, response);
}
}
}
}
package org.malred.learnjwtsecurity.security;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
@Override
protected void configure(HttpSecurity http) throws Exception {
// ...
http.addFilterBefore(new CustomAuthorizationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
// ...
}
刷新 token
package org.malred.learnjwtsecurity.api;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.malred.learnjwtsecurity.domain.Role;
import org.malred.learnjwtsecurity.domain.User;
import org.malred.learnjwtsecurity.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.util.*;
import java.util.stream.Collectors;
import static java.util.Arrays.stream;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class UserResource {
private final UserService userService;
// ...
@GetMapping("/token/refresh")
public void refreshToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
String authorizationHeader = request.getHeader(AUTHORIZATION);
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
try {
// 获取携带的token
String refresh_token = authorizationHeader.substring("Bearer ".length());
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
JWTVerifier verifier = JWT.require(algorithm).build();
// 解码jwt并取数据
DecodedJWT decodedJWT = verifier.verify(refresh_token);
String username = decodedJWT.getSubject();
User user = userService.getUser(username);
String access_token = JWT.create()
.withSubject(user.getUsername())
// 过期时间 一分钟
.withExpiresAt(new Date(System.currentTimeMillis() + 10 * 60 * 1000))
.withIssuer(request.getRequestURL().toString())
.withClaim("roles", user.getRoles().stream()
.map(Role::getName)
.collect(Collectors.toList()))
.sign(algorithm);
Map<String, String> tokens = new HashMap<>();
tokens.put("access_token", access_token);
tokens.put("refresh_token", refresh_token);
response.setContentType(APPLICATION_JSON_VALUE);
// 用json格式返回
new ObjectMapper().writeValue(response.getOutputStream(), tokens);
} catch (Exception e) {
response.setHeader("error", e.getMessage());
response.setStatus(FORBIDDEN.value()); // 403
Map<String, String> errors = new HashMap<>();
errors.put("error_message", e.getMessage());
response.setContentType(APPLICATION_JSON_VALUE);
// 用json格式返回
new ObjectMapper().writeValue(response.getOutputStream(), errors);
}
} else {
throw new RuntimeException("Refresh token is missing");
}
}
}
@Data
class RoleToUserForm {
private String username;
private String rolename;
}
如果 access_token 过期了,而 refresh_token 还没,则使用 refresh_token 去请求刷新接口,然后继续使用刷新接口新生成的 access_token