基于Springboot、Oauth2.0、JWT、封装登录签名基本业务模型


底部附源码地址

功能介绍

  1.  集成验证码登录认证
  2. 加入密码输入错误次数超过限制次数,锁定账号
  3.  加入用户登录日志
  4.  基于Oauth2协议的密码登录和token刷新

项目结构

一个公共模块、一个sso模块、可以新加项目增加自己的业务模块

服务搭建

父级引入pom

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.yx</groupId>
    <artifactId>auth-platform</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>auth-common</module>
        <module>auth-sso</module>
    </modules>
    <name>auth-platform</name>
    <description>登录认证系统</description>

    <properties>
        <fast.version>1.0.0</fast.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
        <druid.version>1.2.16</druid.version>
        <bitwalker.version>1.21</bitwalker.version>
        <swagger.version>3.0.0</swagger.version>
        <kaptcha.version>2.3.3</kaptcha.version>
        <fastjson.version>2.0.39</fastjson.version>
        <oshi.version>6.4.4</oshi.version>
        <commons.io.version>2.13.0</commons.io.version>
        <commons.collections.version>3.2.2</commons.collections.version>
        <poi.version>4.1.2</poi.version>
        <velocity.version>2.3</velocity.version>
        <jwt.version>0.9.1</jwt.version>
        <mybatis-plus.version>3.5.2</mybatis-plus.version>
        <lombok.version>1.18.10</lombok.version>
        <hutool.version>5.1.0</hutool.version>
        <redisson.version>3.11.0</redisson.version>
        <spring-boot-version>2.5.15</spring-boot-version>
        <oauth2.version>2.2.6.RELEASE</oauth2.version>
    </properties>

    <!-- 依赖声明 -->
    <dependencyManagement>
        <dependencies>
            <!-- SpringBoot的依赖配置-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot-version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- 阿里数据库连接池 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid.version}</version>
            </dependency>

            <dependency>
                <groupId>org.springframework.security.oauth.boot</groupId>
                <artifactId>spring-security-oauth2-autoconfigure</artifactId>
                <version>${oauth2.version}</version>
            </dependency>

            <!--工具包-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool.version}</version>
            </dependency>

            <!-- io常用工具类 -->
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>${commons.io.version}</version>
            </dependency>

            <!-- collections工具类 -->
            <dependency>
                <groupId>commons-collections</groupId>
                <artifactId>commons-collections</artifactId>
                <version>${commons.collections.version}</version>
            </dependency>

            <!-- 阿里JSON解析器 -->
            <dependency>
                <groupId>com.alibaba.fastjson2</groupId>
                <artifactId>fastjson2</artifactId>
                <version>${fastjson.version}</version>
            </dependency>

            <!-- Token生成与解析-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>${jwt.version}</version>
            </dependency>
            <!-- 解析客户端操作系统、浏览器等 -->
            <dependency>
                <groupId>eu.bitwalker</groupId>
                <artifactId>UserAgentUtils</artifactId>
                <version>${bitwalker.version}</version>
            </dependency>
            <!-- 验证码 -->
            <dependency>
                <groupId>pro.fessional</groupId>
                <artifactId>kaptcha</artifactId>
                <version>${kaptcha.version}</version>
            </dependency>

            <!-- 公共模块-->
            <dependency>
                <groupId>com.yx</groupId>
                <artifactId>auth-common</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <!-- 认证模块-->
            <dependency>
                <groupId>com.yx</groupId>
                <artifactId>auth-sso</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    </dependencyManagement>



    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <!--依赖下载仓库地址-->
    <repositories>
        <repository>
            <id>public</id>
            <name>ali  nexus</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>public</id>
            <name>ali  nexus</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
</project>

公共模块pom依赖

<!-- Spring框架基本的核心工具 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--常用工具类 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <!--工具包-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
        <!-- 阿里JSON解析器 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
        </dependency>
        <!-- JSON工具类 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.10.1</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>31.1-android</version>
        </dependency>

        <!-- redis 缓存操作 -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>${redisson.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <!-- Mysql驱动包 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

SSO模块pom依赖

<!-- SpringBoot Web容器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- SpringBoot 拦截器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- Auth2.0-->
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>eu.bitwalker</groupId>
            <artifactId>UserAgentUtils</artifactId>
        </dependency>
        <!-- 阿里JSON解析器 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
        </dependency>
        <!-- 验证码 -->
        <dependency>
            <groupId>pro.fessional</groupId>
            <artifactId>kaptcha</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>servlet-api</artifactId>
                    <groupId>javax.servlet</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- pool 对象池 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <!-- 阿里数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <!-- 通用工具-->
        <dependency>
            <groupId>com.yx</groupId>
            <artifactId>auth-common</artifactId>
        </dependency>

核心配置类

JWT配置

package com.yx.config;

import com.yx.exception.MyWebResponseExceptionTranslator;
import com.yx.service.impl.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import javax.sql.DataSource;
import java.util.Arrays;

/**
 * oauth2.0集成JWT
 * @author cyx
 *
 */
@Configuration
@EnableAuthorizationServer
public class JwtAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private DataSource dataSource;
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private TokenEnhancer jwtTokenEnhancer;
    
    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired
    MyUserDetailsService myUserDetailsService;
    

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 让/oauth/token支持client_id以及client_secret作登录认证
        // 请求/oauth/token的,如果配置支持allowFormAuthenticationForClients的,且url中有client_id和client_secret的会走ClientCredentialsTokenEndpointFilter
        security.allowFormAuthenticationForClients();
        security.checkTokenAccess("permitAll()");
        security.tokenKeyAccess("permitAll()");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }

    @Override//
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(jwtTokenStore());
        //设置RefreshTokens为不复用,  true:复用是指每次刷新生成的refreshToken的jti不变,所以过期时间使用的还是第一次生成refreshToken的过期时间
        //false:不复用是每次都生成新的jti,新的过期时间
        endpoints.reuseRefreshTokens(false);
		//指定认证管理器
		endpoints.authenticationManager(authenticationManager);

		TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        enhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer,jwtAccessTokenConverter));
    	endpoints.tokenEnhancer(enhancerChain);
    	endpoints.accessTokenConverter(jwtAccessTokenConverter);
    	// 转换异常信息
    	endpoints.exceptionTranslator(new MyWebResponseExceptionTranslator<>());
    	//增强token 包含用户信息
        endpoints.userDetailsService(myUserDetailsService);

    }
    
    @Primary
    @Bean
    public DefaultTokenServices tokenServices() {
    	DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
    	defaultTokenServices.setTokenStore(jwtTokenStore());
    	defaultTokenServices.setSupportRefreshToken(true);
    	TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        enhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer,jwtAccessTokenConverter));
    	defaultTokenServices.setTokenEnhancer(enhancerChain);
        return defaultTokenServices;
    }

    


    /**
     * JWT token 转换器
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter jatc = new JwtAccessTokenConverter();
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("oauth2.jks"), "LwABFa2JEJR3ezNG".toCharArray());
        jatc.setKeyPair(keyStoreKeyFactory.getKeyPair("oauth2"));
        return jatc;
    }

    /**
     * JWT token Store配置
     * @return
     */
    @Bean
    public JwtTokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * 自定义的Token增强器,把更多信息放入Token中
     * @return
     */
    @Bean
    public TokenEnhancer jwtTokenEnhancer(){
        return new JwtTokenEnhancer();
    }
}

JWT增强

package com.yx.config;

import com.yx.constant.Constants;
import com.yx.model.UserInfo;
import com.yx.manager.AsyncManager;
import com.yx.manager.factory.AsyncFactory;
import com.yx.model.MyUser;
import com.yx.service.impl.SysPasswordService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;

import java.util.HashMap;
import java.util.Map;

/**
 * 扩展JWT附加信息
 * @author cyx
 *
 */
public class JwtTokenEnhancer implements TokenEnhancer {

	@Autowired
	private SysPasswordService sysPasswordService;

	@Override
	public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
		MyUser user = (MyUser)authentication.getUserAuthentication().getPrincipal();
		UserInfo userInfo = user.getUserInfo();
		Map<String,Object> info = new HashMap<>(16);
		info.put("username" , userInfo.getUsername());
		info.put("nickname", userInfo.getNickname());
		info.put("id",userInfo.getId());
        //设置附加信息
        ((DefaultOAuth2AccessToken)accessToken).setAdditionalInformation(info);
        //记录登录日志
		AsyncManager.me().execute(AsyncFactory.recordLoginInfo(userInfo.getUsername(), Constants.LOGIN_SUCCESS, "登录成功"));
		//登录成功,清除错误日志缓存
		sysPasswordService.clearLoginRecordCache(userInfo.getUsername());
		return accessToken;
	}

}

密码错误次数限制

package com.yx.config;

import com.yx.execption.ErrorCodeEnum;
import com.yx.service.impl.SysPasswordService;
import com.yx.utils.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.stereotype.Component;
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;

/**
 * 密码错误次数限制
 */
@Slf4j
@Component
public class LoginRetryCountFilter extends OncePerRequestFilter {


    @Autowired
    private CustomAuthenticationFailureHandler customAuthenticationFailureHandler;


    private final static String OAUTH_TOKEN_URL = "/oauth/token";

    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (request.getServletPath().equals(OAUTH_TOKEN_URL) &&
                StringUtils.equalsAnyIgnoreCase(request.getMethod(), "post")) {
            String username = request.getParameter("username");
            SysPasswordService passwordService = SpringUtils.getBean("sysPasswordService");
            if (passwordService.isLock(username)) {
                customAuthenticationFailureHandler.onAuthenticationFailure(request, response, new BadCredentialsException(ErrorCodeEnum.USER_PASSWORD_MAX_RETRY_COUNT.getMessage()));
                return;
            }
        }
        // 无异常即校验成功,放行。
        filterChain.doFilter(request, response);
    }
}

验证码校验

package com.yx.config;

import com.yx.constant.CacheConstants;
import com.yx.constant.Constants;
import com.yx.execption.ErrorCodeEnum;
import com.yx.manager.AsyncManager;
import com.yx.manager.factory.AsyncFactory;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.stereotype.Component;
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;

@Component
public class ValidateCodeFilter extends OncePerRequestFilter {

    private static final Logger logger = LoggerFactory.getLogger("sys-user");
    @Autowired
    private RedissonClient redissonClient;

    private final static String OAUTH_TOKEN_URL = "/oauth/token";

    private final static String REFRESH_TOKEN = "refresh_token";

    @Value("${sys.account.captchaEnabled:false}")
    private boolean enabled;

    @Autowired
    private CustomAuthenticationFailureHandler customAuthenticationFailureHandler;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // authentication/form是认证时的请求接口,验证码校验只需要匹配这个接口即可
        if (request.getServletPath().equals(OAUTH_TOKEN_URL) &&
                StringUtils.equalsAnyIgnoreCase(request.getMethod(), HttpMethod.POST.toString())) {

            String grantType = request.getParameter("grant_type");
            //如果是刷新token,则不需要校验验证码
            if (!REFRESH_TOKEN.equals(grantType)) {
                //获取验证码开关
                if (enabled) {
                    // 从redis中获取验证码
                    String id = request.getParameter("id");
                    // 从客户端接收到的验证码
                    String captchaParam = request.getParameter("code");
                    if (StringUtils.isEmpty(id)) {
                        customAuthenticationFailureHandler.onAuthenticationFailure(request, response, new BadCredentialsException("缺少必要的参数"));
                        return;
                    }
                    if (StringUtils.isEmpty(captchaParam)) {
                        customAuthenticationFailureHandler.onAuthenticationFailure(request, response, new BadCredentialsException(ErrorCodeEnum.CAPTCHA_NULL_EXCEPTION.getMessage()));
                        return;
                    }
                    //获取验证码
                    RBucket<String> bucket = redissonClient.getBucket(CacheConstants.CAPTCHA_CODE_KEY + id);
                    String code = bucket.get();
                    logger.debug("登录获取验证码:key:{},code:{}", id, code);
                    if (!StringUtils.equalsAnyIgnoreCase(captchaParam, code)) {
                        customAuthenticationFailureHandler.onAuthenticationFailure(request, response, new BadCredentialsException(ErrorCodeEnum.CAPTCHA_ERROR_EXCEPTION.getMessage()));
                        AsyncManager.me().execute(AsyncFactory.recordLoginInfo(request.getParameter("username"), Constants.LOGIN_FAIL, "验证码错误"));
                        return;
                    }
                }
            }
        }
        // 无异常即校验成功,放行。
        filterChain.doFilter(request, response);
    }
}

请求案例

通过密码模式获取令牌

http://localhost:9091/oauth/token?password=admin123&grant_type=password&client_id=pda_client&client_secret=user123&captcha=123&id=fb400c0b672c4be39b8c422edb2f801&username=admin2


刷新Token

http://localhost:9091/oauth/token?grant_type=refresh_token&client_id=pda_client&client_secret=user123&refresh_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbjIiLCJzY29wZSI6WyJhbGwiXSwiYXRpIjoiNDUxN2NlNTUtNWNjNS00ODI5LWJkM2QtOWM4NjJkMWE0OTcyIiwibmlja25hbWUiOiJhZG1pbiIsImlkIjoyLCJleHAiOjE2OTc3MDA3MTEsImp0aSI6IjQyYjU2ZDhlLTViMGMtNDA4Mi1hODFmLWMzMDBhOWI5YjkzZCIsImNsaWVudF9pZCI6InBkYV9jbGllbnQiLCJ1c2VybmFtZSI6ImFkbWluMiJ9.D1xjWEuhtoCKpMBJKjauBBvYRBIBmOx3ItckyDns5qIOf_sUTNDtSgGlQ-4kCfIBBDSD23g4sTw2mvkMB5Ur3w

不足之处多多指教

Gitee项目地址

还有什么常见的功能。欢迎留下宝贵意见!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值