学成在线-第17天-讲义-用户认证 Zuul 一

用户认证 
1.1 用户认证流程分析 
用户认证流程如下:
 
业务流程说明如下: 
1、客户端请求认证服务进行认证。 
2、认证服务认证通过向浏览器cookie写入token(身份令牌
认证服务请求用户中心查询用户信息。 
认证服务请求Spring Security申请令牌。 
认证服务将token(身份令牌)jwt令牌存储至redis中。 
认证服务向cookie写入 token(身份令牌)。 
3、前端携带token请求认证服务获取jwt令牌 
前端获取到jwt令牌并存储在sessionStorage。 
前端从jwt令牌中解析中用户信息并显示在页面。 
4、前端携带cookie中的token身份令牌及jwt令牌访问资源服务 
前端请求资源服务需要携带两个token,一个是cookie中的身份令牌,一个是http header中的jwt令牌 
前端请求资源服务前在http header上添加jwt请求资源 
5、网关校验token的合法性

用户请求必须携带token身份令牌和jwt令牌 
网关校验redistoken是否合法,已过期则要求用户重新登录 
6、资源服务校验jwt的合法性并完成授权 
资源服务校验jwt令牌,完成授权,拥有权限的方法正常执行,没有权限的方法将拒绝访问。 
1.2 认证服务查询数据库 
1.2.1 需求分析 
认证服务根据数据库中的用户信息去校验用户的身份,即校验账号和密码是否匹配。 
认证服务不直接连接数据库,而是通过用户中心服务去查询用户中心数据库。
 

1.2.2 搭建环境 
1.2.2.1 创建用户中心数据库 
用户中心负责用户管理,包括:用户信息管理、角色管理、权限管理等。 
创建xc_user数据库(MySQL) 
导入xc_user.sql(已导入不用重复导入)

 
1.2.2.2 创建用户中心工程 
导入资料”-xc-service-ucenter.zip
 

1.2.4 查询用户接口 
完成用户中心根据账号查询用户信息接口功能。 
1.2.4.1 Api接口 
用户中心对外提供如下接口: 
1、响应数据类型

此接口将来被用来查询用户信息及用户权限信息,所以这里定义扩展类型

@Data
@ToString
public class XcUserExt extends XcUser {
//权限信息
private List<XcMenu> permissions;
//企业信息
private String companyId;
}

2、根据账号查询用户信息

@Api(value = "用户中心",description = "用户中心管理")
public interface UcenterControllerApi {
public XcUserExt getUserext(String username);
}

1.2.4.2 DAO 
添加XcUserXcCompantUser两个表的Dao

public interface XcUserRepository extends JpaRepository<XcUser, String> {
XcUser findXcUserByUsername(String username);
}
public interface XcCompanyUserRepository extends JpaRepository<XcCompanyUser,String> {
//根据用户id查询所属企业id
XcCompanyUser findByUserId(String userId);
}

1.2.4.2 Service

@Service
public class UserService {
@Autowired
private XcUserRepository xcUserRepository;
//根据用户账号查询用户信息
public XcUser findXcUserByUsername(String username){
return xcUserRepository.findXcUserByUsername(username);
}
//根据账号查询用户的信息,返回用户扩展信息
public XcUserExt getUserExt(String username){
XcUser xcUser = this.findXcUserByUsername(username);
if(xcUser == null){
return null;
}
XcUserExt xcUserExt = new XcUserExt();
BeanUtils.copyProperties(xcUser,xcUserExt);
//用户id
String userId = xcUserExt.getId();
//查询用户所属公司
XcCompanyUser xcCompanyUser = xcCompanyUserRepository.findXcCompanyUserByUserId(userId);
if(xcCompanyUser!=null){
String companyId = xcCompanyUser.getCompanyId();
xcUserExt.setCompanyId(companyId);
}
return xcUserExt;
}
}

1.2.4.3 Controller

@RestController
@RequestMapping("/ucenter")
public class UcenterController implements UcenterControllerApi {
@Autowired
UserService userService;
@Override
@GetMapping("/getuserext")
public XcUserExt getUserext(@RequestParam("username") String username) {
XcUserExt xcUser = userService.getUserExt(username);
return xcUser;
}
}

1.2.4.4 测试 
使用Swagger-uipostman测试用户信息查询接口 
1.2.5 调用查询用户接口 
1.2.5.1 创建client 
认证服务需要远程调用用户中心服务查询用户,在认证服务中创建Feign客户端

@FeignClient(value = XcServiceList.XC_SERVICE_UCENTER)
public interface UserClient {
@GetMapping("/ucenter/getuserext")
public XcUserExt getUserext(@RequestParam("username") String username)
}

1.2.5.2 UserDetailsServiceImpl
认证服务调用spring security接口申请令牌,spring security接口会调用UserDetailsServiceImpl从数据库查询用户,如果查询不到则返回 NULL,表示不存在;在UserDetailsServiceImpl中将正确的密码返回, spring security会自动去比对输入密码的正确性。
1、修改UserDetailsServiceImplloadUserByUsername方法,调用Ucenter服务的查询用户接口

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
UserClient userClient;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//取出身份,如果身份为空说明没有认证
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//没有认证统一采用httpbasic认证,httpbasic中存储了client_id和client_secret,开始认证
client_id和client_secret
if(authentication==null){
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(username);
if(clientDetails!=null){
//密码
String clientSecret = clientDetails.getClientSecret();
return new
User(username,clientSecret,AuthorityUtils.commaSeparatedStringToAuthorityList(""));
}
}
if (StringUtils.isEmpty(username)) {
return null;
}
//请求ucenter查询用户
XcUserExt userext = userClient.getUserext(username);
if(userext == null){
//返回NULL表示用户不存在,Spring Security会抛出异常
return null;
}
//从数据库查询用户正确的密码,Spring Security会去比对输入密码的正确性
String password = userext.getPassword();
String user_permission_string = "";
UserJwt userDetails = new UserJwt(username,
password,
AuthorityUtils.commaSeparatedStringToAuthorityList(user_permission_string));
//用户id
userDetails.setId(userext.getId());
//用户名称
userDetails.setName(userext.getName());
//用户头像
userDetails.setUserpic(userext.getUserpic());
//用户所属企业id
userDetails.setCompanyId(userext.getCompanyId());
return userDetails;
}
}

2、测试,请求http://localhost:40400/auth/userlogin 
观察UserDetailsServiceImpl是否正常请求Ucenter的查询用户接口。 
1.2.5.3 BCryptPasswordEncoder 
早期使用md5对密码进行编码,每次算出的md5值都一样,这样非常不安全,Spring Security推荐使用 
BCryptPasswordEncoder对密码加随机盐,每次的Hash值都不一样,安全性高。 
1BCryptPasswordEncoder测试程序如下

@Test
public void testPasswrodEncoder(){
String password = "111111";
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
for(int i=0;i<10;i++) {
//每个计算出的Hash值都不一样
String hashPass = passwordEncoder.encode(password);
System.out.println(hashPass);
//虽然每次计算的密码Hash值不一样但是校验是通过的
boolean f = passwordEncoder.matches(password, hashPass);
System.out.println(f);
}
}

2、在AuthorizationServerConfifig配置类中配置BCryptPasswordEncoder

//采用bcrypt对密码进行Hash
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

3、测试 
请求http://localhost:40400/auth/userlogin,输入正常的账号和密码进行测试 
1.2.5.4 解析申请令牌错误信息 
当账号输入错误应该返回用户不存在的信息,当密码错误要返回用户名或密码错误信息,业务流程图如下:
 

修改申请令牌的程序解析返回的错误
由于restTemplate收到400401的错误会抛出异常,而spring security针对账号不存在及密码错误会返回400及 
401,所以在代码中控制针对400401的响应不要抛出异常。

......
Map map = null;
try {
((RestTemplate) restTemplate).setErrorHandler(new DefaultResponseErrorHandler() {
@Override
public void handleError(ClientHttpResponse response) throws IOException {
// 设置 当响应400和401时照常响应数据,不要报错
if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401 ) {
super.handleError(response);
}
}
});
//http请求spring security的申请令牌接口
ResponseEntity<Map> mapResponseEntity = restTemplate.exchange(path, HttpMethod.POST, new
HttpEntity<MultiValueMap<String, String>>(formData, header), Map.class);
map = mapResponseEntity.getBody();
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("request oauth_token_password error: {}",e.getMessage());
e.printStackTrace();
ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
}
if(map == null ||
map.get("access_token") == null ||
map.get("refresh_token") == null ||
map.get("jti") == null){//jti是jwt令牌的唯一标识作为用户身份令牌
//获取spring security返回的错误信息
String error_description = (String) map.get("error_description");
if(StringUtils.isNotEmpty(error_description)){
if(error_description.equals("坏的凭证")){
ExceptionCast.cast(AuthCode.AUTH_CREDENTIAL_ERROR);
}else if(error_description.indexOf("UserDetailsService returned null")>=0){
ExceptionCast.cast(AuthCode.AUTH_ACCOUNT_NOTEXISTS);
}
}
ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
}
......


 
1.2.5.5 测试 
使用postman请求http://localhost:40400/auth/userlogin 
1、输入正确的账号和密码进行测试 
从数据库找到测试账号,本课程所提供的用户信息初始密码统一为111111 
2、输入错误的账号和密码进行测试 

扫描二维码关注公众号,回复: 9265257 查看本文章
发布了835 篇原创文章 · 获赞 152 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/qq_40208605/article/details/104393705