如何引入SpringSecurity作为项目的权限认证服务
1.引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
2.配置4个类
内容资源
3.简单测试
@Slf4j
@RestController
public class LoginController {
@Autowired
XcUserMapper userMapper;
@RequestMapping("/login-success")
public String loginSuccess() {
return "登录成功";
}
@RequestMapping("/user/{id}")
public XcUser getuser(@PathVariable("id") String id) {
XcUser xcUser = userMapper.selectById(id);
return xcUser;
}
}
什么是OAuth2?
OAuth2是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。
1它为用户资源的授权提供了一个安全的、开放而又建议的标准。OAuth2的作用是为了解决访问资源的安全性以及灵活性。
2.它定义了通过access token去获取请求资源的机制,但是没有定义提供用户身份信息的标准方法。
3.OAuth2可以用来实现 secure delegated access,基于HTTPS,以及APIs,Service应用使用 access token 来进行身份验证
OAuth2的模式有四种,分别是授权码模式、简化模式、密码模式和客户端模式。最常用的就是授权码模式和密码模式
授权码模式典型例子就是微信扫码认证,自己可以点进链接详细了解具体实现。
在SpringSecurity中统一认证入口,如何做到统一的?
(就像你登录游戏可以有多种登录方式,如扫码登录,账号密码登录)
1.新建一个对象类,不管什么类型的登录前端传入的参数必须一致
2.新建一个实现UserDetailsService 的实现类,重写loadUserByUsername方法,该方法就是整个程序的统一入口,一旦点击登录就会先调用该方法,可以在该方法里编写逻辑
/**
* @author Mr.M
* @version 1.0
* @description 认证用户请求参数
* @date 2022/9/29 10:56
*/
@Data
public class AuthParamsDto {
private String username; //用户名
private String password; //域 用于扩展
private String cellphone;//手机号
private String checkcode;//验证码
private String checkcodekey;//验证码key
private String authType; // 认证的类型 password:用户名密码模式类型 sms:短信模式类型
private Map<String, Object> payload = new HashMap<>();//附加数据,作为扩展,不同认证类型可拥有不同的附加数据。如认证类型为短信时包含smsKey : sms:3d21042d054548b08477142bbca95cfa; 所有情况下都包含clientId
}
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xuecheng.ucenter.mapper.XcUserMapper;
import com.xuecheng.ucenter.model.dto.AuthParamsDto;
import com.xuecheng.ucenter.model.dto.XcUserExt;
import com.xuecheng.ucenter.model.po.XcUser;
import com.xuecheng.ucenter.service.AuthService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.List;
//统一认证入口,账号密码、微信扫码登录
@Slf4j
@Component
public class UserServiceImpl implements UserDetailsService {
@Autowired
XcUserMapper xcUserMapper;
@Autowired
ApplicationContext applicationContext;
// 以后传入的对象就是AuthParamsDto,根据参数的不同来识别用户的登录类型
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//1.统一的请求参数,获取并转成实体类
AuthParamsDto authParamsDto =null;
try {
authParamsDto = JSON.parseObject(s, AuthParamsDto.class);
} catch (Exception e) {
throw new RuntimeException("请求认证不符合参数要求");
}
//2.wx,password,third,根据不同的登录类型走不同的实现类
String authType = authParamsDto.getAuthType();
//3.不同的登录方法对数据库操作的逻辑不一样,如微信扫码(获取第三方access token就可以获取信息保存到数据库)和账号密码登录(验证账号密码的正确性,然后查库获取信息)验证的逻辑不一样
String beanName=authType+"_authService";
AuthService authService = applicationContext.getBean(beanName, AuthService.class);
XcUserExt xcUserExt = authService.execute(authParamsDto);
//4.根据类型执行验证逻辑,给前端封装令牌
UserDetails userDetails = getUserPrincipal(xcUserExt);
return userDetails;
}
private UserDetails getUserPrincipal(XcUserExt xcUser) {
String password = xcUser.getPassword();
xcUser.setPassword(null);
//把用户信息转成json放入令牌中,方便其它服务拿到
String userJson = JSON.toJSONString(xcUser);
// 根据用户id查询全息
String [] authorities={"test"};
List<String> list=xcUserMapper.selectAuthoritiesByUserId(xcUser.getId());
if (list.size()>0){
authorities= list.toArray(new String[list.size()]);
}
//权限
UserDetails userDetails = User.withUsername(userJson).password(password).authorities(authorities).build();
return userDetails;
}
}
对于第三步,设计就是一个接口不同的实现类根据@Service(“xxx_authService”)的值走不同的逻辑
账号密码登录逻辑
package com.xuecheng.ucenter.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xuecheng.ucenter.feignclient.CheckCodeClient;
import com.xuecheng.ucenter.mapper.XcUserMapper;
import com.xuecheng.ucenter.model.dto.AuthParamsDto;
import com.xuecheng.ucenter.model.dto.XcUserExt;
import com.xuecheng.ucenter.model.po.XcUser;
import com.xuecheng.ucenter.service.AuthService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
//账号密码登录
@Service("password_authService")
public class PasswordAuthServiceImpl implements AuthService {
@Autowired
XcUserMapper xcUserMapper;
//框架自带
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
CheckCodeClient checkCodeClient;
@Override
public XcUserExt execute(AuthParamsDto authParamsDto) {
//账号
String username = authParamsDto.getUsername();
//验证码 todo
//前端输入的验证码
String checkcode = authParamsDto.getCheckcode();
//验证码存储的key
String checkcodekey = authParamsDto.getCheckcodekey();
if (StringUtils.isEmpty(checkcode)||StringUtils.isEmpty(checkcodekey)){
throw new RuntimeException("请输入验证码");
}
Boolean verify = checkCodeClient.verify(checkcodekey, checkcode);
if (verify==null||!verify){
throw new RuntimeException("验证码输入错误");
}
//1.s就是username,根据username查询数据库,查询用户是否存在
XcUser xcUser = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>().eq(XcUser::getUsername, username));
//2.查询为空报错
if (xcUser==null){
throw new RuntimeException("账号不存在");
}
//3.数据库密码
String password = xcUser.getPassword();
//校验
String inputPassword = authParamsDto.getPassword();
boolean matches = passwordEncoder.matches(inputPassword, password);
if (!matches){
throw new RuntimeException("账号或密码错误");
}
XcUserExt xcUserExt = new XcUserExt();
BeanUtils.copyProperties(xcUser, xcUserExt);
return xcUserExt;
}
}
微信登录的逻辑(另一个实现类)
@Override
public XcUserExt execute(AuthParamsDto authParamsDto) {
//得到账号
String username = authParamsDto.getUsername();
//查询数据库
XcUser xcUser = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>().eq(XcUser::getUsername, username));
if(xcUser == null){
throw new RuntimeException("用户不存在");
}
XcUserExt xcUserExt = new XcUserExt();
BeanUtils.copyProperties(xcUser, xcUserExt);
return xcUserExt;
}
如何根据SpringSecurity框架自带的功能提供资源权限验证?
如普通员工不能调用查看员工工资,经理却可以。
1.准备条件,引入了SpringSecurity并且要进行权限控制的接口的服务配置了SpringSecurity的配置
数据表
2.在controller类中使用
@PreAuthorize(“hasAnyAuthority(‘xc_teachmanager_course_list’)”)//指定权限标识符
只要当它携带的令牌中有该权限才能访问
在已经有了SpringSecurity微服务的前提下,其它微服务的接口想进行权限限制,需要在这些微服务中集成OAuth2。这样,当用户访问这些微服务时,就可以通过OAuth2进行身份验证和授权。
引入配置
package com.xuecheng.content.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import java.util.Arrays;
/**
* @author Administrator
* @version 1.0
**/
@Configuration
public class TokenConfig {
String SIGNING_KEY = "mq123";
// @Bean
// public TokenStore tokenStore() {
// //使用内存存储令牌(普通令牌)
// return new InMemoryTokenStore();
// }
@Autowired
private JwtAccessTokenConverter accessTokenConverter;
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}
注意:权限在UserDetailsService的实现类中设置这个是在SpringSecurity的服务中,其它微服务不需要要修改,这里只是注明
也是在数据库中查询该用户的权限