赞
踩
写在前面:当我们开始使用Security的时候,常常因为各种注入,而迷糊,但是很害怕使用security,感觉很重很难用!再加入oauth2更难搞,今天我就写点自己的配置理解,希望对大家有帮助!(会提供一个可以使用的源码,放在最后,如果你不想看内容,可以直接下载!)
我们主要用认证 ——授权 ——发放Token
1.核心依赖(主要就是这两个,其他的随便导下就行了,reids,mysql的自己弄下)
- <!--security依赖-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-security</artifactId>
- </dependency>
- <!--oauth2依赖-->
- <dependency>
- <groupId>org.springframework.security.oauth</groupId>
- <artifactId>spring-security-oauth2</artifactId>
- <version>2.3.3.RELEASE</version>
- </dependency>
2.Yml配置文件(为了节省空间,其他配置不放了,redis,mysql的大家自己根据自己情况使用)
- # oauth2.0配置
- client:
- oauth2:
- client-id: appId # 客户端标识Id
- secret: 123456 # 客户端安全码
- # 授权类型
- grant_types:
- - password
- - refresh_token
- # token 有效期
- token-validity-time: 3600
- refresh-token-validity-time: 3600
- # 客户端访问范围
- scopes:
- - api
- - all
3.下面就开始核心代码编写了
第一就是security的配置类,该类相当核心,主要注入几个核心功能,先贴代码
- package io.springboot.netty.oauth2server;
-
- import org.apache.commons.codec.digest.DigestUtils;
- 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.password.PasswordEncoder;
- import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
-
- import javax.annotation.Resource;
-
- /**
- * Security 配置类
- *
- * @author zheng
- */
- @Configuration
- @EnableWebSecurity
- public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
-
- /**
- * 注入redis 连接工厂
- */
- @Resource
- private RedisConnectionFactory redisConnectionFactory;
-
- /**
- * 初始化 redisTokenStore 用户将token 放入redis
- * @return
- */
- @Bean
- public RedisTokenStore redisTokenStore(){
- RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
- redisTokenStore.setPrefix("TOKEN:");
- return redisTokenStore;
- }
-
- /**
- * 初始化密码编码器 MD5加密
- * @return
- */
- @Bean
- public PasswordEncoder passwordEncoder(){
- return new PasswordEncoder() {
-
- /**
- * 加密
- * @param rawPassword 原始密码
- * @return
- */
- @Override
- public String encode(CharSequence rawPassword) {
- return DigestUtils.md5Hex(rawPassword.toString());
- }
-
- /**
- * 校验密码
- * @param rawPassword 原始密码
- * @param encoderPassword 加密后密码
- * @return
- */
- @Override
- public boolean matches(CharSequence rawPassword, String encoderPassword) {
- return DigestUtils.md5Hex(rawPassword.toString()).equals(encoderPassword);
- }
- };
- }
-
- /**
- * 初始化管理对象
- */
- @Override
- @Bean
- public AuthenticationManager authenticationManager() throws Exception {
- return super.authenticationManager();
- }
-
- /**
- * 放行 和 认证规则
- * @param http
- * @throws Exception
- */
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.csrf().disable()
- .authorizeRequests()
- // 放行
- .antMatchers("/oauth/**", "/actuator/**")
- .permitAll()
- .and()
- .authorizeRequests()
- .anyRequest()
- // 其他需要拦截
- .authenticated();
-
- }
- }
1.RedisConnectionFactory 注入redis的连接工厂,
2.初始化 redisTokenStore 用户将token 放入redis,也可以放入内存当中,建议放入redis中。
3.初始化密码编码器,使用MD5,亦可以换成security的加密方式。重写加密方法-encode,修改加密方式,重写校验方法-matches.
4.初始化 AuthenticationManager认证管理对象
5.重写config方法,配置放行和认证 、/oauth 等会我们重写它,做为登录方法使用
"/oauth/**", "/actuator/**"
这个方法还是比较简单的,主要为了注入bean和配置一个拦截方法
授权配置类
-
-
- import io.springboot.netty.entity.LoginUser;
- import io.springboot.netty.service.impl.UserServiceImpl;
- import org.springframework.beans.factory.annotation.Autowired;
- 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 zheng
- */
- @Configuration
- @EnableAuthorizationServer
- public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
-
- @Autowired
- private UserServiceImpl userService;
-
- @Resource
- private RedisTokenStore redisTokenStore;
-
-
-
- /**
- * 管理器
- */
- @Resource
- private AuthenticationManager authenticationManager;
-
- /**
- * 密码编码器
- */
- @Resource
- private PasswordEncoder passwordEncoder;
-
- /**
- * 客户端配置类
- */
- @Resource
- private ClientOauth2DataConfiguration oauth2DataConfiguration;
-
- /**
- * 客户端配置授权模型
- * @param clients
- * @throws Exception
- */
- @Override
- public 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
- */
- @Override
- public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
- // 允许访问 token 的公钥,默认 /oauth/token_key 受保护的
- security.tokenKeyAccess("permitAll()")
- // 允许访问 token 的状态,默认 /oauth/check_token 受保护的
- .checkTokenAccess("permitAll()");
- }
-
- @Override
- public 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.getUserId());
- map.put("usernmae",loginUser.getUsername());
- map.put("logo", loginUser.getLogo());
- DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
- token.setAdditionalInformation(map);
- return token;
- });
-
- }
- }
这个方法中,我们可以看到上一个方法注入的几个bean,并且重写三个配置类1.config,configure(ClientDetailsServiceConfigurer clients),客户端配置授权模型,就是我们所说oauth2的四个模式,我们就用一个密码模式。
2.configure(AuthorizationServerSecurityConfigurer security),配置令牌端点的安全约束,上面我们也提到了重写/oauth/**接口来实现登录,在这里要先给它放行,否则我们都请求不到!
3.configure(AuthorizationServerEndpointsConfigurer endpoints),这里就是配置认证器了,我们先将管理器注入(authenticationManager),然后实现具体登录方法,主要就是为了登录校验密码,然后设置token的存放方式,最后对令牌做一个增强,就是要返回那些信息。
UserServiceImpl和ClientOauth2DataConfiguration,我们不知道是干嘛的,其实userServiceImpl不说都很清楚,就是为了校验登录用的,后面这个是为了加载client.ouath2的yml数据的,数据在上面的YML文件中
-
-
- import lombok.Data;
- import org.springframework.boot.context.properties.ConfigurationProperties;
- import org.springframework.stereotype.Component;
-
- /**
- * 客户端数据配置类
- * @author zheng
- */
- @Component
- @ConfigurationProperties(prefix = "client.oauth2")
- @Data
- public class ClientOauth2DataConfiguration {
-
-
- private String clientId;
-
- private String secret;
-
- private String[] grantTypes;
-
- private int tokenValidityTime;
-
- private int refreshTokenValidityTime;
-
- private String[] scopes;
- }
UserServiceImpl 实现UserDetailes.类,重写了loadUserName方法
-
-
- import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import org.springframework.beans.BeanUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.core.userdetails.UsernameNotFoundException;
- import security.oauth2.entity.LoginUser;
- import security.oauth2.entity.ManorUser;
- import security.oauth2.mapper.ManorUserMapper;
-
- public class UserServiceImpl extends ServiceImpl<ManorUserMapper, ManorUser> implements UserService, UserDetailsService {
-
- @Autowired
- private ManorUserMapper userMapper;
-
- /**
- * Security 用户登陆实现方法
- * @param username
- * @return
- * @throws UsernameNotFoundException
- */
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- ManorUser user = userMapper.selectOne(new LambdaQueryWrapper<ManorUser>().eq(ManorUser::getUserId, username));
- LoginUser loginUser = new LoginUser();
- // 复制用户信息
- BeanUtils.copyProperties(user,loginUser);
- // return new User(username,user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(null));
- return loginUser;
- }
- }
ManorUser是数据库表中用户表的实体类,LoginUser需要实现UserDetails中的几个方法
-
-
- import org.springframework.security.core.userdetails.UserDetails;
-
- import com.baomidou.mybatisplus.annotation.TableId;
- import com.google.common.collect.Lists;
- import lombok.Getter;
- import lombok.Setter;
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.security.core.GrantedAuthority;
- import org.springframework.security.core.authority.AuthorityUtils;
- import org.springframework.security.core.authority.SimpleGrantedAuthority;
-
- import java.util.Collection;
- import java.util.Date;
- import java.util.List;
- import java.util.stream.Collectors;
- import java.util.stream.Stream;
-
- /**
- * @author zheng
- */
- @Getter
- @Setter
- public class LoginUser implements UserDetails {
-
- /**
- *
- */
- @TableId
- private Integer id;
- /**
- * 是否需要引导(默认为0,需要 1:不需要)
- */
- private Integer guideType;
- /**
- * 等级
- */
- private Integer level;
- /**
- * 用户经验值
- */
- private Integer experience;
-
- /**
- * 种植水果类型 (默认0:未种 1.桃树,2.梨树,3.西瓜,4.草莓,5.葡萄)
- */
- private Integer fruitsType;
-
- /**
- * 果树等级
- */
- private Integer fruitsLevel;
- /**
- * 果树经验值
- */
- private Integer fruitsExp;
- /**
- * 用户id
- */
- private Integer userId;
-
- /**
- * 弹幕开启:0 关闭 1:开启
- */
- private Integer setBulletArr;
-
- private Date createTime;
-
- private Integer state;
-
- /**
- * 肥料
- */
- private Integer manureNum;
-
- /**
- * 水
- */
- private Integer waterNum;
-
- /**
- * 加速卡
- */
- private Integer cardNum;
-
- /**
- * 当月前用户签到总数
- */
- private Integer addSign;
-
- private String logo;
-
- private String username;
-
- private String password;
-
- private String roles;
-
- private List<GrantedAuthority> authorities;
-
- //** 获取角色信息
- @Override
- public Collection<? extends GrantedAuthority> getAuthorities() {
- if (StringUtils.isNotBlank(this.roles)){
- //获取数据库中角色
- Lists.newArrayList();
- 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;
- }
-
- @Override
- public String getPassword() {
- return this.password;
- }
-
- @Override
- public String getUsername() {
- return this.username;
- }
-
- @Override
- public boolean isAccountNonExpired() {
- return true;
- }
-
- @Override
- public boolean isAccountNonLocked() {
- return true;
- }
-
- @Override
- public boolean isCredentialsNonExpired() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return this.state == 0 ? false : true;
- }
- }
-
配置完成上面这些东西,我们就可以获取了TOKEN了,我们来重写一个登录方法,写一个验证接口
- package security.oauth2.controller;
-
-
- import lombok.SneakyThrows;
- 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.web.HttpRequestMethodNotSupportedException;
- import org.springframework.web.bind.annotation.PostMapping;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestParam;
- import org.springframework.web.bind.annotation.RestController;
-
- import javax.annotation.Resource;
- import java.security.Principal;
- import java.util.LinkedHashMap;
- import java.util.Map;
-
- /**
- * @author zheng
- */
- @RestController
- @RequestMapping("oauth")
- public class OauthController {
-
- /**
- * 返回增强
- */
- @Resource
- private TokenEndpoint tokenEndpoint;
-
-
- @PostMapping("token")
- public Map<String, Object> postAccessToken(Principal principal, @RequestParam Map<String,String> param) throws HttpRequestMethodNotSupportedException {
- Map<String, Object> map = custom(tokenEndpoint.postAccessToken(principal, param).getBody());
- return map;
- }
-
- 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;
- }
-
- }
感兴趣的朋友可以去查查看令牌增强的用处,其实就是返回Token的使用,可以带点你自定义的信息给页面仔用。
我们来使用来测试下,我用的APIpost,你也可以用其他的测试工具
图上可以看下参数,账号密码,没什么说的,grant_type = password,就是我上面提到的密码模式,scope,指的是接口可访问的范围,这两个参数在YML文件中,都配置了,在请求的时需要传递,在Body旁边,有个认证,postman中为英文认证单词,点击进入
这两个参数需要从yml读取,也就是client-id和secret的值,我这里选择配置文件加载,主要就是为了验证前后端是否有资格使用登录接口,如果配置的值不一致,也是请求不了接口的。还有一种方式是从数据库中加载,security也提供了相应的方式,更加灵活点。
- {
- "userId": 10148629,
- "usernmae": "抽奖专用小马甲",
- "logo": "http://logo.sqsjt.net/upfiles/user/face/90/9005c1be3839c52c52c97e7589e46d5c.jpg?5F1FF05C",
- "accessToken": "5bb9602b-89bb-42d6-a4b6-3d35db9bea2a",
- "expireIn": 2489,
- "scopes": [
- "api"
- ],
- "refreshToken": "0979b94e-3f85-4e5b-afa2-719f0ae17bb2"
- }
请求成功后,就是返回了这些,从参数可以看到我们实现的令牌增强功能,返回了自定义的三个参数,userId,username,logo,可以配置更多参数,剩下的token参数则是在 OauthController.custom中配置了。
请求成功后,我们可以在redis中看到Token的相关信息,如下图
可是我们用token去请求接口的时候,还是会提示403,找不到相关资源,这个是因为我们还没有配置资源配置类
- package security.oauth2.oauth2Server;
-
- 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 zheng
- */
- @Configuration
- @EnableResourceServer
- public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
-
- @Resource
- private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
-
- @Resource
- private RedisTokenStore redisTokenStore;
-
- @Override
- public void configure(HttpSecurity http) throws Exception {
- http.authorizeRequests().anyRequest()
- .authenticated()
- .and()
- .requestMatchers().antMatchers("/user/**");
- }
-
- @Override
- public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
- // 设置token存储
- resources.tokenStore(redisTokenStore);
- // 设置验证失败
- resources.authenticationEntryPoint(myAuthenticationEntryPoint);
- }
- }
重写config方法,验证/user/**开头的请求接口,这里可以根据具体业务配置,我们写一个UserController,来查询下用户的基本信息。
- package security.oauth2.controller;
-
- import org.springframework.security.core.Authentication;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RestController;
-
-
- /**
- * 用户中心
- * @author zheng
- */
- @RestController
- public class UserController {
-
- @GetMapping("user/me")
- public Object postAccessToken(Authentication authentication){
- Object principal = authentication.getPrincipal();
- return principal;
- }
- }
直接请求接口,使用/oauth/token返回的access_token,可以看出来,返回了用户基本信息
用户表结构
- /*
- Navicat Premium Data Transfer
-
- Source Server : localhost
- Source Server Type : MySQL
- Source Server Version : 50725
- Source Host : localhost:3306
- Source Schema : yami_shops
-
- Target Server Type : MySQL
- Target Server Version : 50725
- File Encoding : 65001
-
- Date: 24/08/2021 00:33:39
- */
-
- SET NAMES utf8mb4;
- SET FOREIGN_KEY_CHECKS = 0;
-
- -- ----------------------------
- -- Table structure for manor_user
- -- ----------------------------
- DROP TABLE IF EXISTS `manor_user`;
- CREATE TABLE `manor_user` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `guide_type` tinyint(1) DEFAULT 0 COMMENT '是否需要引导(默认为0,需要 1:不需要)',
- `level` int(255) DEFAULT 1 COMMENT '用户等级',
- `experience` int(255) DEFAULT 0 COMMENT '用户经验值',
- `fruits_type` tinyint(1) DEFAULT 0 COMMENT '种植水果类型 (默认0:未种 1.桃树,2.梨树,3.西瓜,4.草莓,5.葡萄)',
- `fruits_exp` int(255) DEFAULT 0 COMMENT '果树经验值',
- `user_id` int(20) NOT NULL COMMENT '用户id',
- `fruits_level` tinyint(2) DEFAULT 0 COMMENT '果树等级',
- `set_bullet_arr` tinyint(1) DEFAULT 1 COMMENT '弹幕开启:0 关闭 1:开启',
- `create_time` datetime(0) DEFAULT NULL COMMENT '创建时间',
- `state` tinyint(1) DEFAULT 1 COMMENT '状态:1 启用 0 禁用',
- `manure_num` int(11) DEFAULT 0 COMMENT '肥料',
- `water_num` int(11) DEFAULT 0 COMMENT '水',
- `card_num` int(11) DEFAULT NULL COMMENT '加速卡',
- `add_sign` int(255) DEFAULT 0 COMMENT '本月前用户签到总数',
- `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户名',
- `logo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '头像',
- `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '密码',
- PRIMARY KEY (`id`) USING BTREE,
- UNIQUE INDEX `user_id`(`id`, `user_id`) USING BTREE
- ) ENGINE = InnoDB AUTO_INCREMENT = 20210513 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '广电庄园 —— 用户信息表' ROW_FORMAT = Dynamic;
-
- -- ----------------------------
- -- Records of manor_user
- -- ----------------------------
- INSERT INTO `manor_user` VALUES (20210511, 1, 30, 1268710, 1, 71120, 10148629, 7, 0, '2021-06-02 11:39:33', 1, 245, 910, 2, 7, '抽奖专用小马甲', 'xxxxxxxxxxxxxxx', 'e10adc3949ba59abbe56e057f20f883e');
-
- SET FOREIGN_KEY_CHECKS = 1;
到这里,我们可以看出来,集成已经完成,其实也没有几个类,就搞定了token的分发,大家可以直接用项目源码去自己调试一下,每隔模块都写得很清晰。
demo,我已经测试成功!!!,所以出现问题,一定是你没有配置好,请不要说项目不能使用的话。虚心接受指导,但不接受瞎xxxxx
码云地址:https://gitee.com/jijiuc/oauth2_securtiy.git
土豪地址:https://download.csdn.net/download/weixin_38061191/21472467
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。