一.项目结构介绍

源码下载

直接看图就行:

关键点都在config包里面,那我们看看config:

很明了,

  • Excepition 是自定义异常封装类
  • redis redis缓存配置
  • security spring-security-oauth2相关配置,最最最重要部分
  • swagger knife4j接口开发文档
  • Application.yml spring配置文件(redis、数据源、oauth2等)

二.创建项目,这里是idea开发工具

左上角: File>New>Project

然后直接Next(这里你可能会遇到https://start.spring.io连接很慢或者直接连接失败,导致创建项目走不了下一步,那你可以选择Custom,输入https://start.aliyun.com):

这是使用阿里云的:https://start.aliyun.com,当然创建后的项目可能不太一样,Application.yml阿里云应该是application.properties文件代替,其实都是大同小异的,语法稍微有些差别

继续填好项目名称,包名等,记得Type选择Maven Next:

勾选依赖(按实际情况,也可后续在pom.xml里面手动灵活添加,也建议这么做),然后一直Next按照提示就创建完成了:

三.写代码前先搞好配置和下载好依赖,也就是pom文件啦~

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.love</groupId><artifactId>my-project</artifactId><version>1.0-SNAPSHOT</version><properties><mybatis.plus.version>3.5.2</mybatis.plus.version></properties><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.5.RELEASE</version></parent><dependencies><dependency><groupId>org.springframework.plugin</groupId><artifactId>spring-plugin-core</artifactId><version>2.0.0.RELEASE</version></dependency><dependency><groupId>org.springframework.plugin</groupId><artifactId>spring-plugin-metadata</artifactId><version>2.0.0.RELEASE</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.25</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis.plus.version}</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>2.0.8</version></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-micro-spring-boot-starter</artifactId><version>2.0.8</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope></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-security</artifactId></dependency><!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 --><dependency><groupId>org.springframework.security.oauth</groupId><artifactId>spring-security-oauth2</artifactId><version>2.3.3.RELEASE</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.0.5.RELEASE</version></dependency></dependencies>
</project>

稍微讲解一下啊?那好:

1.mybatis-plus相关依赖

mybatis-plus版本号属性独立配置,mysql版本8.0.25,driver配置后面要注意

<properties><mybatis.plus.version>3.5.2</mybatis.plus.version>
</properties><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.25</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis.plus.version}</version></dependency>

2.lombok

Lombok项目是一个java库,它可以自动插入到编辑器和构建工具中,增强java的性能。不需要再写getter、setter或equals方法,只要有一个注解,你的类就有一个功能齐全的构建器、自动记录变量等等。

 <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>

3.swagger 开发文档

前后端分离开发少不了~
配置也很简单~,稍等,后面说道

<dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>2.0.8</version>
</dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-micro-spring-boot-starter</artifactId><version>2.0.8</version>
</dependency>

swagger长这样:

4.spring-security,oauth2集成依赖

这里可能你会遇到版本号或者各种冲突,那可以灵活更换依赖就行,冷静思考,多百度一下就行~

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 --><dependency><groupId>org.springframework.security.oauth</groupId><artifactId>spring-security-oauth2</artifactId><version>2.3.3.RELEASE</version></dependency>

5.redis集成依赖

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.0.5.RELEASE</version></dependency>

三.pom完美解决了,接着就是Application.yml配置了

还是乖乖贴完整的好~

server:port: 8089
#  path: /loveerror:include-stacktrace: neverspring:datasource:url: jdbc:mysql://localhost:3306/sys?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&useAffectedRows=true&allowPublicKeyRetrieval=trueusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driverredis:# Redis数据库索引(默认为0)database: 0# Redis服务器地址host: 127.0.0.1# Redis服务器连接端口port: 6379# Redis服务器连接密码(默认为空)password: root# 连接池最大连接数(使用负值表示没有限制)max-active: 20# 连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: 1# 连接池中的最大空闲连接max-idle: 0# 连接池中的最小空闲连接min-idle: 0# 连接超时时间(毫秒)timeout: 1000mybatis-plus:mapper-locations: classpath*:mapper/*.xmltype-aliases-package: com.love.*.mapper# oauth2.0配置
client:oauth2:# 客户端标识Idclient-id: appId# 客户端安全码secret: 123456# 授权类型grant_types:- password- refresh_token# token 有效期token-validity-time: 3600refresh-token-validity-time: 3600# 客户端访问范围scopes:- api- all

好像可以说点什么,好像也没什么好说的,那还是说点吧~

1.项目访问端口配置8089

2.项目访问前缀路径path,如:/love,即是:http://127.0.0.1:8089/love

3.数据源配置,注意driver:com.mysql.cj.jdbc.Driver

4.redis配置看注释~

5.mybatis-plus包扫描配置,通配符扫描,mapper里的java和resources下面的mapper里的xml最好名称一致,会自动映射,没必要搞特殊找麻烦~

6.oauth2.0配置,接口请求要声明:客户端标识Id和客户端安全码,与配置一致,否则无权访问,看下图明了(postman截图):

grant_types分为password,也就账密登录的意思,refresh_token比较特殊,大概可以理解为:token过期后通过refresh_token授权刷新token来续费,也就是告诉你没钱了,赶紧交钱,不然停止服务~

token有效期,3600秒,一个钟

四.redis配置类,序列化存储的关键,其实网上很多,类似

package com.love.config.redis;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** @author hjf* @date 2022-10-24 10:33* @describe redis配置*/@Configuration //使用注解注入配置,必须要添加,这样application.properties中的配置才能在redis中生效,添加@Configuration之后,spring 会自动扫描注入
public class RedisConfig {@Bean@SuppressWarnings("all")public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {// 我们为了自己开发方便,一般直接使用 <String,Object>RedisTemplate<String, Object> template = new RedisTemplate<String,Object>();template.setConnectionFactory(factory);// Json序列化配置Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);// String 的序列化StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();// key采用String的序列化方式template.setKeySerializer(stringRedisSerializer);// hash的key也采用String的序列化方式template.setHashKeySerializer(stringRedisSerializer);// value序列化方式采用jacksontemplate.setValueSerializer(jackson2JsonRedisSerializer);// hash的value序列化方式采用jacksontemplate.setHashValueSerializer(jackson2JsonRedisSerializer);template.afterPropertiesSet();return template;}
}

五.AuthorizationServerConfiguration实现类

token存储userId,userName和头像avatar

package com.love.config.security;import com.love.entity.vo.LoginUser;
import com.love.entity.base.ClientOauth2DataConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
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.store.redis.RedisTokenStore;import javax.annotation.Resource;
import java.util.LinkedHashMap;/*** @author hjf* @date 2022-10-24 14:10* @describe 授权配置类*/@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {@Resourceprivate UserDetailsServiceImpl userService;@Resourceprivate RedisTokenStore redisTokenStore;/*** 管理器*/@Resourceprivate AuthenticationManager authenticationManager;/*** 密码编码器*/@Resourceprivate PasswordEncoder passwordEncoder;/*** 客户端配置类*/@Resourceprivate ClientOauth2DataConfiguration oauth2DataConfiguration;/*** 客户端配置授权模型* @param clients* @throws Exception*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory().withClient(oauth2DataConfiguration.getClientId()).secret(passwordEncoder.encode(oauth2DataConfiguration.getSecret())).authorizedGrantTypes(oauth2DataConfiguration.getGrantTypes()) // token 授权类型.accessTokenValiditySeconds(oauth2DataConfiguration.getTokenValidityTime()) // token 过期时间.refreshTokenValiditySeconds(oauth2DataConfiguration.getRefreshTokenValidityTime()) // token 刷新过期时间.scopes(oauth2DataConfiguration.getScopes());}/*** 配置令牌端点的安全约束* @param security* @throws Exception*/@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {// 允许访问 token 的公钥,默认 /oauth/token_key 受保护的security.tokenKeyAccess("permitAll()")// 允许访问 token 的状态,默认 /oauth/check_token 受保护的.checkTokenAccess("permitAll()");}@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {// 认证器endpoints.authenticationManager(authenticationManager)// 具体登陆方法.userDetailsService(userService)// token 存储方式 redis.tokenStore(redisTokenStore)// 令牌增强对象 , 增强返回的结果.tokenEnhancer((accessToken, authentication) ->{// 获取用户信息,然后设置LoginUser loginUser = (LoginUser) authentication.getPrincipal();LinkedHashMap<String, Object> map = new LinkedHashMap<>();map.put("userId",loginUser.getId());map.put("userName",loginUser.getUsername());map.put("avatar", loginUser.getAvatar());DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;token.setAdditionalInformation(map);return token;});}}

重要点
private UserDetailsServiceImpl userService;

自定义类UserDetailsServiceImpl实现security的UserDetailsService,并且重写方法loadUserByUsername:

package com.love.config.security;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.love.entity.vo.LoginUser;
import com.love.entity.UserInfo;
import com.love.service.UserInfoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;/*** @author hjf* @date 2022-10-24 14:24* @describe*/@Slf4j
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {private final UserInfoService userInfoService;/*** 用户密码登录* @param username 用户名* @return UserDetails* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 根据用户名查询用户的接口QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();wrapper.eq("user_name", username);UserInfo userInfo = userInfoService.getOne(wrapper);if (userInfo == null) {throw new UsernameNotFoundException("用户名不存在!");}return getUserDetails(userInfo);}/*** 构建用户信息* @param userInfo* @return 用户详情*/private LoginUser getUserDetails(UserInfo userInfo) {// UserVO是用户实体类,AuthUserDetails是SpringSecurity认证用户详情对象LoginUser userDetail = new LoginUser();// 1. 用户详情封装(此处由于是继承关系,可以使用属性复制的方式)BeanUtils.copyProperties(userInfo, userDetail);return userDetail;}
}

逻辑很简单,根据用户名username数据库查询是否存在用户,不存在直接抛对应异常,你会看到这里为什么没有密码判断,按理说登录应该是账号密码一起判断才对,然后会提示:账号或密码错误 之类的~稍等,主要是security帮你处理掉了,后面说道

六.ResourceServerConfig资源配置类,对外开放访问资源

package com.love.config.security;import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;import javax.annotation.Resource;/*** @author hjf* @date 2022-10-24 18:09* @describe 资源配置类*/@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {@Resourceprivate RedisTokenStore redisTokenStore;@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().requestMatchers().antMatchers("/user/**");}@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {// 设置token存储resources.tokenStore(redisTokenStore);}
}

若没有配置路径,结果:
当然配置了,除了白名单,其他也还要带上token访问才行~

{"timestamp": "2022-11-07T08:04:02.264+0000","status": 403,"error": "Forbidden","message": "Access Denied","path": "/user/getUserInfoById"
}

七.SecurityConfiguration,白名单配置放行,自定义加密配置

package com.love.config.security;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;import javax.annotation.Resource;/*** @author hjf* @date 2022-10-24 14:23* @describe*/@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {/*** 注入redis 连接工厂*/@Resourceprivate RedisConnectionFactory redisConnectionFactory;/*** 初始化 redisTokenStore 用户将token 放入redis* @return*/@Beanpublic RedisTokenStore redisTokenStore(){RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);redisTokenStore.setPrefix("TOKEN:");return redisTokenStore;}/*** http请求设置*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests()// 放行.antMatchers("/oauth/**","/actuator/**","/doc.html/**","/favicon.ico","/webjars/**","/swagger-resources/**","/v2/api-docs/**").permitAll().and().authorizeRequests().anyRequest()// 其他需要拦截.authenticated();}/*** 初始化管理对象*/@Override@Beanpublic AuthenticationManager authenticationManager() throws Exception {return super.authenticationManager();}/*** 密码加密算法* @return 加密算法,BCrypt实现加密器可以有效防止撞库*/@Beanpublic PasswordEncoder passwordEncoder(){PasswordEncoder encoder = new BCryptPasswordEncoder();return encoder;}}

token存放key:
redisTokenStore.setPrefix(“TOKEN:”);
看看都存了什么?下图,真实产品肯定没必要存这么多,这里就可以再深入自定义处理了,暂时到这里先吧~

密码怎么加密,这里提高一个test测试类:

package com.love;import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)
public class UserInfoTest {@Testpublic void getUserInfoVo() {String myPassword = "";//你的密码//密码加密myPassword = new BCryptPasswordEncoder().encode(myPassword );System.out.println("myPassword ="+myPassword );}
}

八.swagger配置,两个类,几乎复制拷贝就行~

SwaggerBootstrapUiDemoApplication:

package com.love.config.swagger;import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@SpringBootApplication
public class SwaggerBootstrapUiDemoApplication  implements WebMvcConfigurer {@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");}
}

SwaggerConfig:

package com.love.config.swagger;import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;/*** @author hjf* @date 2022-10-11 12:11* @describe Swagger开发文档*/
@Configuration
@EnableSwagger2WebMvc
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
public class SwaggerConfig {@Beanpublic Docket createRestApi() {return new Docket(DocumentationType.SWAGGER_2).useDefaultResponseMessages(false).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.basePackage("com.love")).paths(PathSelectors.any()).build();}private ApiInfo apiInfo() {return new ApiInfoBuilder().version("1.0.0").title("爱芳芳-Spring Cloud Swagger2 文档").description("爱芳芳-Spring Cloud Swagger2 文档").termsOfServiceUrl("https://blog.csdn.net/lucky_fang?type=blog").build();}
}

记得改成自己的包名~

十.OauthController,登录获取凭证token

package com.love.controller;import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.*;import java.security.Principal;
import java.util.LinkedHashMap;
import java.util.Map;/*** @author hjf* @date 2022-10-24 15:19* @describe 登录认证*/@Api(tags = "权限控制")
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/oauth")
public class OauthController {/*** 返回增强*/private final TokenEndpoint tokenEndpoint;private final TokenStore tokenStore;@PostMapping("/login")public Map<String, Object> postAccessToken(Principal principal, @RequestParam Map<String,String> param) throws HttpRequestMethodNotSupportedException {return custom(tokenEndpoint.postAccessToken(principal, param).getBody());}private Map<String,Object> custom(OAuth2AccessToken oAuth2AccessToken){DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) oAuth2AccessToken;Map<String,Object> data =  new LinkedHashMap(token.getAdditionalInformation());data.put("accessToken",token.getValue());data.put("expireIn", token.getExpiresIn());data.put("scopes", token.getScope());if (token.getRefreshToken() != null){data.put("refreshToken",token.getRefreshToken().getValue());}return data;}/*** 移除access_token和refresh_token,退出登录** @param access_token 登录token*/@DeleteMapping(value = "/removeToken", params = "access_token")public void removeToken(Principal principal, String access_token) {OAuth2AccessToken accessToken = tokenStore.readAccessToken(access_token);if (accessToken != null) {// 移除access_tokentokenStore.removeAccessToken(accessToken);// 移除refresh_tokenif (accessToken.getRefreshToken() != null) {tokenStore.removeRefreshToken(accessToken.getRefreshToken());}}}
}

postman请求截图

记得配置这个,上面已经说过:

退出登录,清除token,redis同时也会清除:

十一.其他杂七杂八的一并乖乖贴上

LoginUser:

package com.love.entity.vo;import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.love.entity.UserInfo;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;/*** @author hjf* @date 2022-10-24 14:27* @describe*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser extends UserInfo implements UserDetails {@ApiModelProperty(value = "凭证")private List<GrantedAuthority> authorities;@Overridepublic String getPassword() {return this.password;}@Overridepublic String getUsername() {return this.userName;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return this.status != 1;}private String roles;//** 获取角色信息@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {if (StringUtils.isNotBlank(this.roles)){//获取数据库中角色this.authorities = Stream.of(this.roles.split(",")).map(role ->{return new SimpleGrantedAuthority(role);}).collect(Collectors.toList());}else{// 如果角色为空this.authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");}return this.authorities;}
}

UserInfo:

package com.love.entity;import com.baomidou.mybatisplus.annotation.TableName;
import com.love.entity.base.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** @author hjf* @date 2022-10-19 10:24* @describe 用户*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(value ="user_info")
public class UserInfo extends BaseEntity {@ApiModelProperty(value = "用户名称")public String userName;@ApiModelProperty(value = "登录密码 加密")public String password;@ApiModelProperty(value = "登录密码 原始密码")public String originalPassword;@ApiModelProperty(value = "头像")public String avatar;/*** {@link com.love.enumerate.Gender}*/@ApiModelProperty(value = "性别")public Integer gender;/*** {@link com.love.enumerate.YesOrNo}*/@ApiModelProperty(value = "状态")public Integer status;@ApiModelProperty(value = "备注")public String remark;}

BaseEntity:

package com.love.entity.base;import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;
import java.time.LocalDateTime;/*** @author hjf* @date 2022-10-19 10:26* @describe 基础类*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BaseEntity implements Serializable {private static final long serialVersionUID = 1L;@TableId(type = IdType.ASSIGN_ID)@ApiModelProperty(name = "id", value = "表主键")public Long id;@ApiModelProperty(name = "deleted", value = "逻辑删除标记 是否已删除: 0否  1是")@TableLogicpublic Integer deleted;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")@JsonDeserialize(using = LocalDateTimeDeserializer.class)@JsonSerialize(using = LocalDateTimeSerializer.class)@TableField(fill = FieldFill.INSERT)@ApiModelProperty(name = "createTime", value = "创建时间")public LocalDateTime createTime;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")@JsonDeserialize(using = LocalDateTimeDeserializer.class)@JsonSerialize(using = LocalDateTimeSerializer.class)@TableField(fill = FieldFill.INSERT_UPDATE)@ApiModelProperty(name = "updateTime", value = "修改时间")public LocalDateTime updateTime;
}

UserInfoMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.love.mapper.UserInfoMapper"><!--详情--><select id="getByUserName" resultType="com.love.entity.UserInfo">selectid,user_name,password,avatar,gender,remark,status,deleted,create_time,update_timefromuser_infowhereuser_name = #{userName}limit 1</select><!--详情--><select id="getById" resultType="com.love.entity.UserInfo">selectid,user_name,password,avatar,gender,remark,status,deleted,create_time,update_timefromuser_infowhereid = #{id}limit 1</select></mapper>

UserInfoMapper.java:

package com.love.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.love.entity.UserInfo;
import org.apache.ibatis.annotations.Param;/*** @author hjf* @date 2022-10-19 10:26* @describe 用户mapper*/
public interface UserInfoMapper extends BaseMapper<UserInfo> {/*** 用户详情** @param userName 用户名* @return UserInfo*/UserInfo getByUserName(@Param("userName") String userName);/*** 用户详情** @param id 用户ID* @return UserInfo*/UserInfo getById(@Param("id") Long id);
}

UserInfoService:

package com.love.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.love.entity.base.Result;
import com.love.entity.UserInfo;/*** @author hjf* @date 2022-10-19 10:26* @describe 用户service*/
public interface UserInfoService extends IService<UserInfo> {/*** 用户登录** @return Result<UserInfo>*/Result<UserInfo> login(String userName,String password);/*** 用户详情** @param id* @return Result<UserInfo>*/Result<UserInfo> getUserInfoById(Long id);
}

UserInfoServiceImpl:

package com.love.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.love.entity.UserInfo;
import com.love.entity.base.Result;
import com.love.enumerate.YesOrNo;
import com.love.mapper.UserInfoMapper;
import com.love.service.UserInfoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;/*** @author hjf* @date 2022-10-19 10:26* @describe 用户service*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {private final UserInfoMapper userInfoMapper;/*** 用户登录** @return Result<UserInfo>*/@Overridepublic Result<UserInfo> login(String userName,String password) {UserInfo userInfo = userInfoMapper.getByUserName(userName);if(userInfo != null){if(userInfo.getDeleted().equals(YesOrNo.YES.getValue())){return Result.failMsg("登录失败,账号已注销");}if(userInfo.getStatus().equals(YesOrNo.YES.getValue())){return Result.failMsg("登录失败,账号已禁用,请联系客服人员");}PasswordEncoder encoder = new BCryptPasswordEncoder();boolean matches = encoder.matches(password, userInfo.getPassword());if (!matches) {return Result.failMsg("账号或密码错误");}return Result.OK(userInfo);}else{return Result.failMsg("账号或密码错误");}}/*** 获取用户详情** @return Result<UserInfo>*/@Overridepublic Result<UserInfo> getUserInfoById(Long id) {//方式1//UserInfo userInfo = getById(id);//方式2//QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();//wrapper.eq("id", id);//UserInfo userInfo = getOne(wrapper);//方式3UserInfo userInfo = userInfoMapper.getById(id);if(userInfo != null){return Result.OK(userInfo);}return Result.fail();}
}

UserInfoController:

package com.love.controller;import com.love.entity.base.Result;
import com.love.entity.UserInfo;
import com.love.entity.vo.LoginUser;
import com.love.service.UserInfoService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;/*** @author hjf* @date 2022-10-19 10:26* @describe 用户controller*/
@Api(tags = "用户管理")
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/user")
public class UserInfoController {private final UserInfoService userInfoService;@ApiOperation(value = "账密登录", notes = "账密登录")@GetMapping("/login")public Result<UserInfo> login(@ApiParam("用户名") @RequestParam("userName") String userName,@ApiParam("密码") @RequestParam("password") String password) {return userInfoService.login(userName,password);}@ApiOperation(value = "根据ID获取用户", notes = "根据ID获取用户")@GetMapping("/getUserInfoById")public Result<UserInfo> getUserInfoById(@ApiParam("用户ID") @RequestParam("id") Long id) {return userInfoService.getUserInfoById(id);}@ApiOperation(value = "获取登录信息", notes = "获取登录信息")@GetMapping("/me")public Result<LoginUser> postAccessToken(Authentication authentication){LoginUser loginUser = (LoginUser) authentication.getPrincipal();return Result.OK(loginUser);}
}

十二.OAuth2.0简述

OAuth协议的延续版本
OAuth2.0是OAuth协议的延续版本,但不向前兼容OAuth 1.0(即完全废止了OAuth1.0)。 OAuth 2.0关注客户端开发者的简易性。要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限。同时为Web应用,桌面应用和手机,和起居室设备提供专门的认证流程。2012年10月,OAuth 2.0协议正式发布为RFC 6749 [1]

目录

  1. 1 前言
  2. 2 认证授权过程
  3. ▪ 简单历史回顾
  4. ▪ 6种全新流程

OAuth2.0前言

OAuth 1.0已经在IETF(国际互联网工程任务组),编号是RFC5849
这也标志着OAuth已经正式成为互联网标准协议。
OAuth 2.0早已经开始讨论和建立的草案。 OAuth2.0很可能是下一代的“用户验证和授权”标准。现在 百度开放平台, 腾讯开放平台等大部分的开放平台都是使用的OAuth 2.0协议作为支撑。
OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。
OAuth
允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定 服务提供者的数据。每一个令牌授权一个特定的网站(例如, 视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要分享他们的访问许可或他们数据的所有内容。
OAuth是OpenID的一个补充,但是完全不同的服务。
OAuth 2.0
是OAuth协议的下一版本,但不 向后兼容OAuth 1.0。 OAuth 2.0关注客户端开发者的简易性,同时为Web应用,桌面应用和手机,和起居室设备提供专门的认证流程。2012年10月,OAuth 2.0协议正式发布为RFC 6749 [1]
Facebook的新的Graph API只支持OAuth 2.0,Google在2011年3月亦宣布Google API对OAuth 2.0的支持。

OAuth2.0认证授权过程

在认证和授权的过程中涉及的三方包括:
1、 服务提供方,用户使用服务提供方来存储受保护的资源,如照片,视频,联系人列表。
2、用户,存放在服务提供方的受保护的资源的拥有者。
3、客户端,要访问服务提供方资源的 第三方应用,通常是网站,如提供照片打印服务的网站。在认证过程之前,客户端要向 服务提供者申请客户端标识。
使用OAuth进行认证和授权的过程如下所示:
用户想操作存放在服务提供方的资源。
用户登录客户端向服务提供方请求一个临时令牌。
服务提供方验证客户端的身份后,授予一个临时令牌。
客户端获得临时令牌后,将用户引导至服务提供方的授权页面请求用户授权。在这个过程中将临时令牌和客户端的回调连接发送给服务提供方。
用户在服务提供方的网页上输入用户名和密码,然后授权该客户端访问所请求的资源。
授权成功后,服务提供方引导用户返回客户端的网页。
客户端根据临时令牌从服务提供方那里获取访问令牌。
服务提供方根据临时令牌和用户的授权情况授予客户端访问令牌。
客户端使用获取的访问令牌访问存放在服务提供方上的受保护的资源。

OAuth2.0简单历史回顾

OAuth 1.0在2007年的12月底发布并迅速成为工业标准。
2008年6月,发布了OAuth 1.0 Revision A,这是个稍作修改的修订版本,主要修正一个安全方面的漏洞。
2010年四月,OAuth 1.0的终于在IETF发布了,协议编号RFC 5849。
OAuth 2.0的草案是在2011年5月初在IETF发布的。
OAuth is a security protocol that enables users to grant third-party access to their web resources without sharing their passwords.
OAuth是个安全相关的协议,作用在于,使用户授权第三方的应用程序访问用户的web资源,并且不需要向 第三方应用程序透露自己的密码。
OAuth 2.0是个全新的协议,并且不对之前的版本做 向后兼容,然而,OAuth 2.0保留了与之前版本OAuth相同的整体架构。
这个草案是围绕着 OAuth2.0的需求和目标,历经了长达一年的讨论,讨论的参与者来自业界的各个知名公司,包括Yahoo!, Facebook, Salesforce, Microsoft, Twitter, Deutsche Telekom, Intuit, Mozilla, and Google。
OAuth 2.0的新特性:

OAuth2.06种全新流程

User-Agent Flow – 客户端运行于 用户代理内(典型如web浏览器)。
Web Server Flow – 客户端是web服务器程序的一部分,通过http request接入,这是OAuth 1.0提供的流程的简化版本。
Device Flow – 适用于客户端在受限设备上执行操作,但是终端用户单独接入另一台电脑或者设备的浏览器
Username and Password Flow – 这个流程的应用场景是,用户信任客户端处理身份凭据,但是仍然不希望客户端储存他们的用户名和密码,这个流程仅在用户高度信任客户端时才适用。
Client Credentials Flow – 客户端适用它的身份凭据去获取access token,这个流程支持2-legged OAuth的场景。
Assertion Flow – 客户端用assertion去换取access token,比如SAML assertion。
可以通过使用以上的多种流程实现Native应用程序对OAuth的支持(程序运行于 桌面操作系统或移动设备)
application support (applications running on a desktop or mobile device) can be implemented using many of the flows above.
持信人token
OAuth 2.0 提供一种无需加密的认证方式,此方式是基于现存的cookie验证架构,token本身将自己作为secret,通过HTTPS发送,从而替换了通过 HMAC和token secret加密并发送的方式,这将允许使用cURL发起APIcall和其他简单的脚本工具而不需遵循原先的request方式并进行签名。
签名简化:
对于签名的支持,签名机制大大简化,不需要特殊的解析处理,编码,和对参数进行排序。使用一个secret替代原先的两个secret。
短期token和长效的身份凭据
原先的OAuth,会发行一个 有效期非常长的token(典型的是一年有效期或者无有效期限制),在OAuth 2.0中,server将发行一个短有效期的access token和长生命期的refresh token。这将允许客户端无需用户再次操作而获取一个新的access token,并且也限制了access token的有效期。
角色分开
OAuth 2.0将分为两个角色:
Authorization server负责获取用户的授权并且发布token。
Resource负责处理API calls。

SpringBoot+OAuth2+Spring Security+Redis+mybatis-plus+mysql+swagger搭建实现相关推荐

  1. SpringCloud+SpringBoot+OAuth2+Spring Security+Redis实现的微服务统一认证授权

    因为目前做了一个基于Spring Cloud的微服务项目,所以了解到了OAuth2,打算整合一下OAuth2来实现统一授权.关于OAuth是一个关于授权的开放网络标准,目前的版本是2.0,这里我就不多 ...

  2. springboot集成Spring Security oauth2(八)

    由于公司项目需要,进行SpringBoot集成Spring Security oauth2,几乎搜寻网上所有大神的案例,苦苦不能理解,不能完全OK. 以下是借鉴各大神的代码,终于demo完工,请欣赏 ...

  3. springBoot整合spring security+JWT实现单点登录与权限管理前后端分离

    在前一篇文章当中,我们介绍了springBoot整合spring security单体应用版,在这篇文章当中,我将介绍springBoot整合spring secury+JWT实现单点登录与权限管理. ...

  4. springBoot整合spring security+JWT实现单点登录与权限管理前后端分离--筑基中期

    写在前面 在前一篇文章当中,我们介绍了springBoot整合spring security单体应用版,在这篇文章当中,我将介绍springBoot整合spring secury+JWT实现单点登录与 ...

  5. SpringBoot 整合 Spring Security 实现安全认证【SpringBoot系列9】

    SpringCloud 大型系列课程正在制作中,欢迎大家关注与提意见. 程序员每天的CV 与 板砖,也要知其所以然,本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCl ...

  6. SpringBoot集成Spring Security(二)注册 、密码加密、修改密码

    SpringBoot集成Spring Security(一)登录注销 写在前面 上一节创建了项目并且利用Spring Security完成了登录注销功能,这里继续说一下注册.密码加密和找回密码,代码注 ...

  7. SpringBoot集成Spring Security(一)登录注销

    同个人网站 https://www.serendipper-x.cn/,欢迎访问 ! SpringBoot集成Spring Security(二)注册 .密码加密.修改密码 写在前面 Spring S ...

  8. 八、springboot整合Spring Security

    springboot整合Spring Security 简介 Spring Security是一个功能强大且可高度自定义的身份验证和访问控制框架.它是保护基于Spring的应用程序的事实标准. Spr ...

  9. SpringBoot集成Spring Security —— 第二章自动登录

    文章目录 一.修改login.html 二.两种实现方式 2.1 Cookie 存储 2.2 数据库存储 2.2.1 基本原理 2.2.2 代码实现 三.运行程序 在上一章:SpringBoot集成S ...

最新文章

  1. FC3服务器配置一条龙
  2. 新版XenCenter添加剪贴板共享功能
  3. unity Android 指南针,Unity之一天一个技术点(十二)---指南针的实现
  4. JAVA mysql存数组_JAVA数组怎么存放数据库的元素
  5. Django项目--首页静态化
  6. 加密软件漏洞评测系统_调查:加密货币挖矿仍居恶意软件威胁前列
  7. 程序员们记得还是八五年PC登陆我国时候的事?
  8. esri-leaflet入门教程(4)-加载各类图层
  9. Qt5学习笔记之QQ登录界面三:添加图片资源
  10. asp.net千奇百怪的日历
  11. 计算机组成原理(微课版)谭志虎pdf资源
  12. 怀疑安装MySQL之后,导致OrCAD Capture、Allegro就打不开
  13. 笔记本键盘扣安装注意事项(小技巧)
  14. fluent瞬态计算终止条件在哪里设置_基于商用软件FLUENT的LES(大涡模拟)计算教学...
  15. PC平台最佳相片管理软件Picasa
  16. android电容触摸屏的驱动及其上层工作原理,电容触摸屏驱动原理
  17. PHP Imagick发光文字
  18. 软考新思维--2017年上半年信息系统项目管理师上午试题分析与答案(试题1-5题)
  19. Font Awesome 的使用
  20. linux下kegg注释软件,工具篇丨GO和KEGG富集不到通路?快试试这个超赞的功能分析工具吧...

热门文章

  1. Github网页创建分支,下载分支,删除分支
  2. 百度地图API自定义点路书,路书点击事件,路书速度动态改变
  3. 《天涯明月刀手游》背后的王者-TcaplusDB数据库
  4. php mysql抽奖程序_使用jQuery+PHP+Mysql实现抽奖程序
  5. 颜值测试软件99分,心理学:第一眼看到了什么,测你的真实颜值多少分?我居然99分...
  6. 100天精通Python丨黑科技篇 —— 23、千图成像,爱心加倍(程序员的浪漫)
  7. Bose全新真无线系列耳塞在京东小魔方频道上市
  8. 老九C语言41课项目实战-皇帝的后宫
  9. 1997考研阅读Text2翻译
  10. uniapp实现红包动画效果(vue3)