Spring Security整合Jwt

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



客户端...


服务端...
用户登录
用户登录
登录成功,用户信息存储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
|       |           |   └── UserController.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的配置类,可以配置tokenSECRET_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 ,迫不得已引流方式请勿介意。

猜你喜欢

转载自www.cnblogs.com/sddr/p/13375144.html