Spring Security整合Jwt

本次基于Spring Boot整合了Spring Security和Jwt,可以解决前后端分离之后用户认证与授权的问题。在前后端还未分离的时候,对用户进行身份认证大约是这样的。

客
户
端
客户端...
服
务
端
服务端...
用户登录
用户登录
登录成功,用户信息存储session
登录成功,用户信息存储session
返回登录状态,并发送cookie
返回登录状态,并发送cookie
将服务端发来的cookie存储
将服务端发来的cookie存储
携带cookie,发送请求
携带cookie,发送请求
Viewer does not support full SVG 1.1

这种缺点就是身份信息需要客户端和服务器同时存储,当用户基数很大的时候,需要大量的内存来解决这个问题。

在前后端分离之后,基于token的用户身份认证大约是这样的。

客
户
端
客户端...
服
务
端
服务端...
用户登录
用户登录
登录成功,颁发token
登录成功,颁发token
将服务端发来的token存储
将服务端发来的token存储
携带token,发送请求
携带token,发送请求
Viewer does not support full SVG 1.1

这种好处是token只需要存储到客户端,服务端只需要对发来的请求中验证token的有效性。

本次便使用基于token的方式,结合spring security进行一次简单的身份认证与授权。

相关版本信息

名称 版本
IDEA商业版 2020.1
JDK JDK1.8
Maven 3.5.4
Windows 家庭版1903

项目结构

.
├── .idea
├── src
│   └── main
|       ├── java
|       |   └── com
|       |       └── example
|       |           ├── controller
|       |           |   └── HelloResource.java
|       |           ├── filters
|       |           |   └── JwtRequestFilter.java
|       |           ├── model
|       |           |   ├── AuthenticationRequest.java
|       |           |   └── AuthenticationResponse.java
|       |           ├── security
|       |           |   ├── MyUserDetailsService.java
|       |           |   └── SecurityConfigurer.java
|       |           ├── utils
|       |           |   └── JwtUtil.java     
|       |           └── Application.java
│       └── resources
│           └── application.properties
├── test
├── target
├── pom.xml
└── security-jwt.iml 

在pom.xml添加相关jar包

<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>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

创建Application.java

这个其实就是Spring Boot的入口文件,名称不一样也没事,内容也没有改动。

package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

创建SecurityConfigurer.java

这个类是Spring Security的配置类,Spring Boot提倡去掉配置文件,用配置类来代替,道理都差不多,我还是熟悉xml一些。

package com.example.security;


import com.example.filters.JwtRequestFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService myUserDetailsService;

    @Autowired
    JwtRequestFilter jwtRequestFilter;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailsService).passwordEncoder(bCryptpasswordEncoder());
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/authenticate")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public BCryptPasswordEncoder bCryptpasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

创建MyUserDetailsService.java

这个类是通过传来用户的username,返回一个用户对象,这里为了简便没有从数据库进行查询,以后改成从数据库访问用户信息,直接在这里查询并返回一个用户就行了。

这里密码采用了BCR加密。

package com.example.security;

import org.springframework.security.core.userdetails.User;
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.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;

@Service
public class MyUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return new User("foo",new BCryptPasswordEncoder().encode("foo"),new ArrayList<>());
    }
}

创建JwtUtil.java

这个是Jwt的配置类,可以配置token的SECRET_KEY,到期时间等等,更重要的作用是可以生成一个token。

package com.example.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

@Component
public class JwtUtil {

    private String SECRET_KEY = "secret";

    public String extractUsername(String token){
        return extractClaim(token, Claims::getSubject);
    }

    public Date extractExpiration(String token){
        return extractClaim(token,Claims::getExpiration);
    }

    public <T> T extractClaim(String token, Function<Claims,T> claimsResolver){
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }

    public Claims extractAllClaims(String token){
        return Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
    }

    public Boolean isTokenExpired(String token){
        return extractExpiration(token).before(new Date());
    }

    public String generateToken(UserDetails userDetails){
        Map<String,Object> claims = new HashMap<>();
        return createToken(claims,userDetails.getUsername());
    }

    private String createToken(Map<String,Object> claims,String subject){
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis()+100*60*60*10))
                .signWith(SignatureAlgorithm.HS256,SECRET_KEY)
                .compact();
    }
    public Boolean validateToken(String token,UserDetails userDetails){
        final String username = extractUsername(token);
        return (username.equals(userDetails.getUsername())) && (!isTokenExpired(token));
    }

}

说明

本文章原创自我的个人博客 https://srcrs.top ,如需要完整文章内容,以及源码,请访问:https://srcrs.top/posts/202007241.html ,迫不得已引流方式请勿介意。

posted @ 2020-07-24 23:45  srcrs  阅读(425)  评论(0)    收藏  举报