Jwt,Spring Security,Spring Cloud OAuth2,Spring Cloud Security

这篇博客详细介绍了如何在Spring Boot应用中结合JWT、Spring Security和OAuth2实现认证和授权。首先,通过JWTLoginFilter自定义登录验证,生成并返回JWT token。接着,利用JwtAuthenticationFilter验证HTTP请求中的token合法性。最后,通过Spring Security配置整合这些功能,并提供了Postman测试步骤及源代码链接。

首先pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.3.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.springsecurityjwt</groupId>
	<artifactId>springsecurityjwt</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>springsecurityjwt</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<!--<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>-->
		<!--<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>-->
        <security-jwt.version>1.0.9.RELEASE</security-jwt.version>
		<hutool.version>4.5.0</hutool.version>
        <jjwt.version>0.9.0</jjwt.version>
    </properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
            <version>${security-jwt.version}</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>${jjwt.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.7.0</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>2.7.0</version>
		</dependency>
		<!-- 超级工具类 hutool -->
		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>${hutool.version}</version>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Dalston.RC1</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>


</project>

测试数据

/*
Navicat MySQL Data Transfer

Source Server         : 本机mysql
Source Server Version : 50713
Source Host           : localhost:3306
Source Database       : zmx

Target Server Type    : MYSQL
Target Server Version : 50713
File Encoding         : 65001

Date: 2019-09-23 15:14:09
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for menu
-- ----------------------------
DROP TABLE IF EXISTS `menu`;
CREATE TABLE `menu` (
  `menu_id` bigint(20) NOT NULL,
  `permission` varchar(255) DEFAULT NULL,
  `url` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of menu
-- ----------------------------
INSERT INTO `menu` VALUES ('4', '所有权限', '/test');

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `role_id` bigint(20) NOT NULL,
  `role_name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('2', '管理员');

-- ----------------------------
-- Table structure for role_menu
-- ----------------------------
DROP TABLE IF EXISTS `role_menu`;
CREATE TABLE `role_menu` (
  `role_menu_id` bigint(20) NOT NULL,
  `menu_id` bigint(20) NOT NULL,
  `role_id` bigint(20) NOT NULL,
  PRIMARY KEY (`role_menu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of role_menu
-- ----------------------------
INSERT INTO `role_menu` VALUES ('5', '4', '2');
INSERT INTO `role_menu` VALUES ('6', '4', '2');

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `user_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `avatar` varchar(255) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `phone` varchar(255) DEFAULT NULL,
  `realname` varchar(255) DEFAULT NULL,
  `sex` varchar(255) DEFAULT NULL,
  `username` varchar(255) DEFAULT NULL,
  `authorities` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', null, null, '123456', null, null, null, 'admin', null);
INSERT INTO `user` VALUES ('2', null, null, '$2a$10$wk3UUqG2IvU67w9Lj/EUOO7lEOB9AMqcXMZO.Lqz2K3NA4WXppNSe', null, null, null, 'user', null);
INSERT INTO `user` VALUES ('3', null, null, '$2a$10$LhBOkwQe9EqqDWezMxm62eyrfUL.9SZnpmBXPDW3zEYaQH1Hwsr8G', null, null, null, 'zhao', null);

-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
  `user_role_id` bigint(20) NOT NULL,
  `role_id` bigint(20) NOT NULL,
  `user_id` bigint(20) NOT NULL,
  PRIMARY KEY (`user_role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES ('3', '2', '1');

 

第一种

一、增加JWT认证功能

用户在调用登录接口,输入用户名和密码之后,如果通过便认证通过。传统的方法是在认证通过之后,创建session并返回客户端cookie。这里采用JWT来处理用户密码的认证。区别在于,认证通过之后服务器生成的是token,将token返回给客户端,客户端以后的所有请求都需要在http头中指定该token。服务器接收请求后,会对token的合法性进行验证。

验证的内容包括:1.内容是一个正确的JWT格式2.检查签名3.检查权限4.处理登录

这一步需要创建一个JWTLoginFilter该类继承UsernamePasswordAuthenticationFilter,核心功能是在验证用户名密码正确之后,生成一个token,并将token返回给客户端

JWTLoginFilter.java

package com.springsecurityjwt.springsecurityjwt.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.springsecurityjwt.springsecurityjwt.constant.ConstantKey;
import com.springsecurityjwt.springsecurityjwt.exception.ExceptionEnum;
import com.springsecurityjwt.springsecurityjwt.exception.UnifiedException;
import com.springsecurityjwt.springsecurityjwt.model.User;
import com.springsecurityjwt.springsecurityjwt.model.UserPrincipal;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.tomcat.util.codec.binary.Base64;
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.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 * 验证用户名密码正确之后,生成一个token,并将token返回给客户端,该方法继承自UsernamePasswordAuthenticationFilter。
 * 重写其中的两个方法attemptAuthentication :接收并解析用户凭证。
 * successfulAuthentication :用户成功登录后,这个方法会被调用,我们在这个方法里生成token。
 *
 * @Author zhaomengxia
 * @create 2019/9/20 14:38
 */
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;
    private static final String JWT_SECRET = ConstantKey.SIGNING_KEY;// JWT密匙
    /**
     * 由字符串生成加密key
     *
     * @return
     */
    public static SecretKey generalKey() {
        String stringKey = JWT_SECRET;
        byte[] encodedKey = Base64.decodeBase64(stringKey);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }
    public JWTLoginFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }


    //接收并解析用户凭证
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            User user = new ObjectMapper().readValue(request.getInputStream(), User.class);

            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            user.getUsername(),
                            user.getPassword(),
                            new ArrayList<>())
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {

        String token = null;
        try {
            Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();

            List roleList = new ArrayList();

            for (GrantedAuthority grantedAuthority :
                    authorities) {
                roleList.add(grantedAuthority.getAuthority());
            }
            Calendar calendar = Calendar.getInstance();
            Date now = calendar.getTime();
            //设置签发时间  当前时间
            calendar.setTime(new Date());

            calendar.add(Calendar.HOUR, 10);//设置10个小时
            Date time = calendar.getTime();
            SecretKey secretKey = generalKey();
            token = Jwts.builder().setSubject(authResult.getName() + "-" + roleList)
                    .setIssuedAt(now)//签发时间
                    .setExpiration(time)//token有效时长
                    .signWith(SignatureAlgorithm.HS256,secretKey)
                    .compact();
            response.addHeader("Authorization", "Bearer " + token);
            System.out.println("Bearer " + token);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

二、授权验证

拿到token之后,请求接口会携带这个token,服务端要验证这个token的合法性。创建一个类JwtAuthenticationFilter并继承BasicAuthenticationFilter。在doFilterInternal方法中,从http头的Authorization项读取token数据,然后使用Jwts包提供的方法校验token的合法性。如果校验通过,就说明这是一个取得授权的合法请求。

JwtAuthenticationFilter.java

package com.springsecurityjwt.springsecurityjwt.filter;

import com.springsecurityjwt.springsecurityjwt.constant.ConstantKey;
import com.springsecurityjwt.springsecurityjwt.exception.TokenException;
import com.springsecurityjwt.springsecurityjwt.service.impl.GrantedAuthorityImpl;
import io.jsonwebtoken.*;
import org.apache.tomcat.util.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;


/**
 * @Author zhaomengxia  这个模块中登录有两种一种是使用spring specurity框架提供的默认登录url localhost:18081/login
 * 使用JwtAuthenticationTwoFilter这一个,注意WebSecurityConfig类里将 .addFilter(new JWTLoginFilter(authenticationManager()))
 *             和 .addFilter(new JwtAuthenticationTwoFilter(authenticationManager()))两个的注释放开,将 http.addFilterBefore(jwtAuthenticationTwoFilter(),UsernamePasswordAuthenticationFilter.class);注释
 *另一种是采用自己写的登录接口 url为http://localhost:18081/user/login 是将WebSecurityConfig中的http.addFilterBefore(jwtAuthenticationTwoFilter(),UsernamePasswordAuthenticationFilter.class);注释放开,将另外两个注释,本项目中是采用这一种方法
 * @create 2019/9/20 15:10
 */
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {

    private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
    // 指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。

    private static final String JWT_SECRET = ConstantKey.SIGNING_KEY;// JWT密匙

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader("Authorization");
        if (header == null || !header.startsWith("Bearer")) {
            chain.doFilter(request, response);
            return;
        }

        UsernamePasswordAuthenticationToken authenticationToken=  getAuthention(request);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        chain.doFilter(request, response);
    }


    private UsernamePasswordAuthenticationToken getAuthention(HttpServletRequest servletRequest){

        long start=System.currentTimeMillis();
        String token=servletRequest.getHeader("Authorization");
        if (token==null||token.isEmpty()){
            throw new TokenException("Token 不能为空!");
        }
        String stringKey = JWT_SECRET;
        String user=null;
        try {
        user= Jwts.parser()
                .setSigningKey(Base64.decodeBase64(stringKey))
                .parseClaimsJws(token.replace("Bearer ", ""))
                .getBody()
                .getSubject();
        long end=System.currentTimeMillis();
        logger.info("执行时间:{}",end-start+"ms");

        if (user!=null){
            String[] split=user.split("-")[1].split(",");
            ArrayList<GrantedAuthority> authorities=new ArrayList<>();
            for (int i=0;i<split.length;i++){
                authorities.add(new GrantedAuthorityImpl(split[i]));
            }
            return new UsernamePasswordAuthenticationToken(user,null,authorities);
        }
        } catch (ExpiredJwtException e) {
            logger.error("Token已过期: {} " + e);
            throw new TokenException("Token已过期");
        } catch (UnsupportedJwtException e) {
            logger.error("Token格式错误: {} " + e);
            throw new TokenException("Token格式错误");
        } catch (MalformedJwtException e) {
            logger.error("Token没有被正确构造: {} " + e);
            throw new TokenException("Token没有被正确构造");
        } catch (SignatureException e) {
            logger.error("签名失败: {} " + e);
            throw new TokenException("签名失败");
        } catch (IllegalArgumentException e) {
            logger.error("非法参数异常: {} " + e);
            throw new TokenException("非法参数异常");
        }

        return null;

    }
}

三、Spring Security

通过配置Spring Security将上面的方法组合在一起。

WebSecurityConfig.java
package com.springsecurityjwt.springsecurityjwt.config;

import com.springsecurityjwt.springsecurityjwt.filter.JWTLoginFilter;
import com.springsecurityjwt.springsecurityjwt.filter.JwtAuthenticationFilter;
import com.springsecurityjwt.springsecurityjwt.filter.JwtAuthenticationTwoFilter;
import com.springsecurityjwt.springsecurityjwt.handler.Http401AuthenticationEntryPoint;
import com.springsecurityjwt.springsecurityjwt.service.impl.CustomAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
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.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.annotation.web.configurers.LogoutConfigurer;
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;

/**
 * @Author zhaomengxia
 * @create 2019/9/16 14:00
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{


    @Bean
    public JwtAuthenticationTwoFilter jwtAuthenticationTwoFilter(){
        return new JwtAuthenticationTwoFilter();
    }

    private static final String[] AUTH_WHITELIST = {
            // -- register url
            "/user/signup",
            "/user/addTask",
            // -- swagger ui
            "/v2/api-docs",
            "/swagger-resources",
            "/swagger-resources/**",
            "/configuration/ui",
            "/configuration/security",
            "/swagger-ui.html",
            "/user/login",
            "/login",
            "/webjars/**"
            // other public endpoints of your API may be appended to this array
    };

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        LogoutConfigurer<HttpSecurity> httpSecurityLogoutConfigurer=http.cors().and()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers(AUTH_WHITELIST).permitAll()
                .anyRequest().authenticated()
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(new Http401AuthenticationEntryPoint("Basic realm=\"MyApp\""))
                .and()
                .addFilter(new JWTLoginFilter(authenticationManager()))
                .addFilter(new JwtAuthenticationFilter(authenticationManager()))
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login")
                .permitAll();

        //http.addFilterBefore(jwtAuthenticationTwoFilter(),UsernamePasswordAuthenticationFilter.class);
    }


    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(new CustomAuthenticationProvider(userDetailsService,bCryptPasswordEncoder));
    }
}
第一种
1.启动SpringsecurityjwtApplication.java类

2.在postman测试,注册一个用户
url:localhost:18081/user/signup
post
header: key--Content-Type ,values--application/json
body:{
       "username":"user",
       "password":"123456"
     }
3. 在postman登录
url: localhost:18081/login    //spring specurity框架提供的默认登录url
post
header: key--Content-Type ,values--application/json
body:{
    "username":"user",
    "password":"123456"
}

token值是 postman返回结果的header里的key--Authorization对应values
  
4. 浏览器访问 http://localhost:18081/swagger-ui.html#
   根据拿到的Authorization对应values
   放到swagger查询用户信息接口http://localhost:18081/user/findByUserName,Authorization,放token
   接着userName对应输入admin
   点击Try it out即可,根据返回结果 可以测试得出拿到的token可用不可用

用postman测试 localhost:18081/login    //spring specurity框架提供的默认登录url

结果

第二种

一、添加类

JwtTokenProvider.java  用户名密码验证通过之后,返回token的
package com.springsecurityjwt.springsecurityjwt.filter;

import com.springsecurityjwt.springsecurityjwt.constant.ConstantKey;
import io.jsonwebtoken.*;
import org.apache.tomcat.util.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 * @Author zhaomengxia
 * @create 2019/9/23 13:30
 */
@Component
public class JwtTokenProvider {
    private static final String JWT_SECRET = ConstantKey.SIGNING_KEY;// JWT密匙
    private static final Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class);
    /**
     * 由字符串生成加密key
     *
     * @return
     */
    public static SecretKey generalKey() {
        String stringKey = JWT_SECRET;
        byte[] encodedKey = Base64.decodeBase64(stringKey);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }
    public String successfulAuthentication(HttpServletResponse response,
                                            Authentication authResult) throws IOException, ServletException {

        String token = null;
        String authentication=null;
        try {
            Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();

            List roleList = new ArrayList();

            for (GrantedAuthority grantedAuthority :
                    authorities) {
                roleList.add(grantedAuthority.getAuthority());
            }
            Calendar calendar = Calendar.getInstance();
            Date now = calendar.getTime();
            //设置签发时间  当前时间
            calendar.setTime(new Date());

            calendar.add(Calendar.HOUR, 10);//设置10个小时
            Date time = calendar.getTime();
            SecretKey secretKey = generalKey();
            token = Jwts.builder().setSubject(authResult.getName() + "-" + roleList)
                    .setIssuedAt(now)//签发时间
                    .setExpiration(time)//token有效时长
                    .signWith(SignatureAlgorithm.HS256,secretKey)
                    .compact();
            authentication="Bearer " + token;
            response.addHeader("Authorization", authentication);
            System.out.println(authentication);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return authentication;
    }
    public String getUserIdFromJWT(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(JWT_SECRET)
                .parseClaimsJws(token)
                .getBody();

        String[] s=claims.getSubject().split("-");

        return s[0];
    }

    public boolean validateToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException ex) {
            logger.error("Invalid JWT signature");
        } catch (MalformedJwtException ex) {
            logger.error("Invalid JWT token");
        } catch (ExpiredJwtException ex) {
            logger.error("Expired JWT token");
        } catch (UnsupportedJwtException ex) {
            logger.error("Unsupported JWT token");
        } catch (IllegalArgumentException ex) {
            logger.error("JWT claims string is empty.");
        }
        return false;
    }
}

二、拿到token之后,每次请求验证token的

JwtAuthenticationTwoFilter.java
package com.springsecurityjwt.springsecurityjwt.filter;

import com.springsecurityjwt.springsecurityjwt.service.impl.UserDetailsServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
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.util.StringUtils;
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;

/**
 * @Author zhaomengxia
 * @create 2019/9/23 13:48
 */
public class JwtAuthenticationTwoFilter extends OncePerRequestFilter {
    @Autowired
    private JwtTokenProvider tokenProvider;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationTwoFilter.class);

    public JwtAuthenticationTwoFilter() {
    }


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            String jwt = getJwtFromRequest(request);

            if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
                String userName = tokenProvider.getUserIdFromJWT(jwt);

                /*
                    Note that you could also encode the user's username and roles inside JWT claims
                    and create the UserDetails object by parsing those claims from the JWT.
                    That would avoid the following database hit. It's completely up to you.
                 */
                UserDetails userDetails = userDetailsService.loadUserByUserName(userName);
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception ex) {
            logger.error("Could not set user authentication in security context", ex);
        }

        filterChain.doFilter(request, response);
    }

    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7, bearerToken.length());
        }
        return null;
    }
}

三、Spring Security的配置

WebSecurityConfig.java
package com.springsecurityjwt.springsecurityjwt.config;

import com.springsecurityjwt.springsecurityjwt.filter.JWTLoginFilter;
import com.springsecurityjwt.springsecurityjwt.filter.JwtAuthenticationFilter;
import com.springsecurityjwt.springsecurityjwt.filter.JwtAuthenticationTwoFilter;
import com.springsecurityjwt.springsecurityjwt.handler.Http401AuthenticationEntryPoint;
import com.springsecurityjwt.springsecurityjwt.service.impl.CustomAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
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.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.annotation.web.configurers.LogoutConfigurer;
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;

/**
 * @Author zhaomengxia
 * @create 2019/9/16 14:00
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{


    @Bean
    public JwtAuthenticationTwoFilter jwtAuthenticationTwoFilter(){
        return new JwtAuthenticationTwoFilter();
    }

    private static final String[] AUTH_WHITELIST = {
            // -- register url
            "/user/signup",
            "/user/addTask",
            // -- swagger ui
            "/v2/api-docs",
            "/swagger-resources",
            "/swagger-resources/**",
            "/configuration/ui",
            "/configuration/security",
            "/swagger-ui.html",
            "/user/login",
            "/login",
            "/webjars/**"
            // other public endpoints of your API may be appended to this array
    };

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        LogoutConfigurer<HttpSecurity> httpSecurityLogoutConfigurer=http.cors().and()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers(AUTH_WHITELIST).permitAll()
                .anyRequest().authenticated()
                .and()
                .exceptionHandling()
                   .authenticationEntryPoint(new Http401AuthenticationEntryPoint("Basic realm=\"MyApp\""))
                .and()
//                .addFilter(new JWTLoginFilter(authenticationManager()))
//                .addFilter(new JwtAuthenticationFilter(authenticationManager()))
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login")
                .permitAll();

        http.addFilterBefore(jwtAuthenticationTwoFilter(),UsernamePasswordAuthenticationFilter.class);
    }


    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(new CustomAuthenticationProvider(userDetailsService,bCryptPasswordEncoder));
    }
}
1.添加JwtTokenProvider.java和JwtAuthenticationTwoFilter.java并继承OncePerRequestFilter类
2.启动SpringsecurityjwtApplication.java类
3.浏览器访问http://localhost:18081/swagger-ui.html#
4.在swagger  注册一个用户信息
5.在swagger 调用http://localhost:18081/user/login这个接口登录,Authorization随便填,用户名密码输入正确即可登陆成功,在Response Body下面即可获得token(accessToken对应的值)

6.在swagger 将5拿到的token用来测试接口http://localhost:18081/user/findByUserName,Authorization放token, 接着userName对应输入刚第4步注册的用户名,即可测试token可用不可用

第5步登录

第6步测试

源代码及数据

https://github.com/zhaomengxia/springboot_mybatis.git 

项目中该模块的位置

 

<think>好的,我现在需要帮助用户在SpringCloud项目中集成SpringSecurity,使用JWTOAuth2实现身份验证和授权。首先,我要回顾一下用户的问题和提供的引用内容,确保回答准确。用户提到他们想了解如何在SpringCloud项目中集成SpringSecurity,结合JWTOAuth2进行身份验证和授权。根据提供的引用,特别是引用[3]和[4],里面提到了OAuth2的配置和JWTToken的生成方式。引用[2]中的依赖是SpringBoot StarterSecurity,这可能需要在项目中引入。引用[5]提到JHipster生成的应用内置了认证和管理功能,这可能是一个参考,但用户可能需要手动配置。 首先,我需要确定基本步骤。通常,集成SpringSecurityOAuth2需要配置授权服务器和资源服务器。使用JWT的话,需要配置TokenStore为JwtTokenStore,并设置JwtAccessTokenConverter,如引用[4]中的代码所示。用户需要添加相关依赖,配置安全规则,定义用户服务,设置JWT签名密钥等。 接下来,我需要考虑如何将这些步骤结构化,分点说明。例如,添加依赖项、配置OAuth2授权服务器、资源服务器配置、JWT配置、用户详细信息服务等。每个步骤都需要具体的代码示例或配置片段,比如引用[4]中的TokenConfig类,展示如何生成JWTToken。同时,用户可能需要了解如何生成和验证JWT令牌,如何保护API端点,以及如何处理OAuth2的授权流程。需要确保引用的内容正确,如引用[3]提到的AuthzAuthenticationFilter和LoginAuthenticationManager,这可能涉及到自定义过滤器的配置,但用户可能更倾向于使用SpringSecurity提供的现有组件,如JwtTokenStore。另外,需要注意在Spring Cloud项目中可能的微服务架构,如何将授权服务器和资源服务器分离。例如,授权服务器负责颁发令牌,各个微服务作为资源服务器验证令牌。这需要正确的跨服务配置,可能涉及公钥和私钥的设置,尤其是在使用JWT时,资源服务器需要能够验证签名,可能使用对称密钥或非对称密钥。 还需要考虑用户详细信息的加载方式,如引用[3]提到的userDatabase,可能需要实现UserDetailsService接口,从数据库或其他存储中加载用户信息。同时,安全配置类需要继承WebSecurityConfigurerAdapter,覆盖configure方法,设置权限规则。 最后,生成相关问题,帮助用户进一步学习,比如如何刷新令牌、分布式环境中的令牌管理、自定义声明等。这些问题应该与当前主题相关,并基于步骤中提到的内容。总结来说,回答需要分步骤,结合代码示例和配置说明,引用提供的资料中的相关内容,确保用户能够按照步骤操作,并理解每个部分的作用。同时,问题需要引导用户深入理解相关概念和高级配置。</think>在Spring Cloud项目中集成Spring Security并实现JWTOAuth2的认证授权,可通过以下步骤完成: --- ### **1. 添加依赖** 在`pom.xml`中引入Spring SecurityOAuth2JWT相关依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.6.8</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> <version>1.1.1.RELEASE</version> </dependency> ``` [^2] --- ### **2. 配置OAuth2授权服务器** 创建授权服务器配置类,启用`@EnableAuthorizationServer`: ```java @Configuration @EnableAuthorizationServer public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("client-id") .secret("{noop}client-secret") .authorizedGrantTypes("password", "refresh_token") .scopes("read"); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { endpoints.authenticationManager(authenticationManager) .accessTokenConverter(jwtAccessTokenConverter); } } ``` --- ### **3. 配置资源服务器** 在需保护的微服务中添加资源服务器配置: ```java @Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/api/**").authenticated(); } } ``` --- ### **4. 配置JWT令牌** 定义JWT生成和验证逻辑,参考引用[4]的`TokenConfig`: ```java @Configuration public class TokenConfig { private static final String SIGNING_KEY = "your-secret-key"; @Bean public TokenStore tokenStore() { return new JwtTokenStore(accessTokenConverter()); } @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey(SIGNING_KEY); // 对称加密密钥 return converter; } } ``` --- ### **5. 用户认证服务** 实现`UserDetailsService`加载用户信息: ```java @Service public class UserService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) { // 从数据库查询用户,示例使用内存用户 return new User("user", "{noop}password", AuthorityUtils.createAuthorityList("ROLE_USER")); } } ``` --- ### **6. 安全配置** 配置全局安全规则: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/oauth/**").permitAll() .anyRequest().authenticated(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } } ``` --- ### **7. 测试令牌获取** 通过`/oauth/token`端点获取JWT令牌: ```bash curl -X POST --user 'client-id:client-secret' \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=password&username=user&password=password" \ http://localhost:8080/oauth/token ``` --- ### **核心原理** - **OAuth2**:通过授权服务器颁发令牌,资源服务器验证令牌[^3]。 - **JWT**:令牌包含签名和用户信息,无需集中存储,适合分布式系统[^4]。 - **Spring Security**:通过过滤器链实现认证和授权逻辑[^1]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值