原文:
https://blog.csdn.net/ech13an/article/details/80779973
思路:
- 搭建springboot工程
- 导入springSecurity跟jwt的依赖
- 用户的实体类
- dao层
- service层(真正开发时再写,这里就直接调用dao层操作数据库)
- 实现UserDetailsService接口
- 实现UserDetails接口
- 验证用户登录信息的拦截器
- 验证用户权限的拦截器
- springSecurity配置
- 认证的Controller以及测试的controller
- 测试
- 享受成功的喜悦
1,导入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
2,jjwt工具
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
3,User类
@Entity
@Table(name = "jd_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
@Column(name = "role")
private String role;
// getter and setter...
}
5,JwtTokenUtils
封装jjwt:
public class JwtTokenUtils {
public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
private static final String SECRET = "jwtsecretdemo";
private static final String ISS = "echisan";
// 过期时间是3600秒,既是1个小时
private static final long EXPIRATION = 3600L;
// 选择了记住我之后的过期时间为7天
private static final long EXPIRATION_REMEMBER = 604800L;
// 创建token
public static String createToken(String username, boolean isRememberMe) {
long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512, SECRET)
.setIssuer(ISS)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.compact();
}
// 从token中获取用户名
public static String getUsername(String token){
return getTokenBody(token).getSubject();
}
// 是否已过期
public static boolean isExpiration(String token){
return getTokenBody(token).getExpiration().before(new Date());
}
private static Claims getTokenBody(String token){
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
}
}
6,UserRespository
public interface UserRepository extends CrudRepository<User, Integer> {
User findByUsername(String username);
}
7,UserDetailsServiceImpl
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userRepository.findByUsername(s);
return new JwtUser(user);
}
}
8.JwtUser
public class JwtUser implements UserDetails {
private Integer id;
private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities;
public JwtUser() {
}
// 写一个能直接使用user创建jwtUser的构造器
public JwtUser(User user) {
id = user.getId();
username = user.getUsername();
password = user.getPassword();
authorities = Collections.singleton(new SimpleGrantedAuthority(user.getRole()));
}
// 获取权限信息,目前博主只会拿来存角色。。
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
// 账号是否未过期,默认是false,记得要改一下
@Override
public boolean isAccountNonExpired() {
return true;
}
// 账号是否未锁定,默认是false,记得也要改一下
@Override
public boolean isAccountNonLocked() {
return true;
}
// 账号凭证是否未过期,默认是false,记得还要改一下
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 这个有点抽象不会翻译,默认也是false,记得改一下
@Override
public boolean isEnabled() {
return true;
}
// 我自己重写打印下信息看的
@Override
public String toString() {
return "JwtUser{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", authorities=" + authorities +
'}';
}
}
9,配置拦截器
JWTAuthenticationFilter
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 从输入流中获取到登录的信息
try {
LoginUser loginUser = new ObjectMapper().readValue(request.getInputStream(), LoginUser.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword(), new ArrayList<>())
);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
// 成功验证后调用的方法
// 如果验证成功,就生成token并返回
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
// 查看源代码会发现调用getPrincipal()方法会返回一个实现了`UserDetails`接口的对象
// 所以就是JwtUser啦
JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
System.out.println("jwtUser:" + jwtUser.toString());
String token = JwtTokenUtils.createToken(jwtUser.getUsername(), false);
// 返回创建成功的token
// 但是这里创建的token只是单纯的token
// 按照jwt的规定,最后请求的格式应该是 `Bearer token`
response.setHeader("token", JwtTokenUtils.TOKEN_PREFIX + token);
}
// 这是验证失败时候调用的方法
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.getWriter().write("authentication failed, reason: " + failed.getMessage());
}
}
JWTAuthorizationFilter
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
// 如果请求头中没有Authorization信息则直接放行了
if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
// 如果请求头中有token,则进行解析,并且设置认证信息
SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
super.doFilterInternal(request, response, chain);
}
// 这里从token中获取用户信息并新建一个token
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
String username = JwtTokenUtils.getUsername(token);
if (username != null){
return new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
}
return null;
}
}```
10,配置SpringSecurity
```java
@EnableWebSecurity
// 至于为什么要配置这个,嘿嘿,卖个关子
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
// 因为UserDetailsService的实现类实在太多啦,这里设置一下我们要注入的实现类
@Qualifier("userDetailsServiceImpl")
private UserDetailsService userDetailsService;
// 加密密码的,安全第一嘛~
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
// 测试用资源,需要验证了的用户才能访问
.antMatchers("/tasks/**").authenticated()
// 其他都放行了
.anyRequest().permitAll()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// 不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
11,AuthController
@RestController
@RequestMapping("/auth")
public class AuthController {
// 为了减少篇幅就不写service接口了
@Autowired
private UserRepository userRepository;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@PostMapping("/register")
public String registerUser(@RequestBody Map<String,String> registerUser){
User user = new User();
user.setUsername(registerUser.get("username"));
// 记得注册的时候把密码加密一下
user.setPassword(bCryptPasswordEncoder.encode(registerUser.get("password")));
user.setRole("ROLE_USER");
User save = userRepository.save(user);
return save.toString();
}
}
12,修改:
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
super.setFilterProcessesUrl("/auth/login");
}
13,测试
@RestController
@RequestMapping("/tasks")
public class TaskController {
@GetMapping
public String listTasks(){
return "任务列表";
}
@PostMapping
public String newTasks(){
return "创建了一个新的任务";
}
@PutMapping("/{taskId}")
public String updateTasks(@PathVariable("taskId")Integer id){
return "更新了一下id为:"+id+"的任务";
}
@DeleteMapping("/{taskId}")
public String deleteTasks(@PathVariable("taskId")Integer id){
return "删除了id为:"+id+"的任务";
}
}