一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第15天,点击查看活动详情。
大家好,我是程序员路飞,三年的下水道打杂前端,喜欢研究技术,正在往全栈发展
欢迎小伙伴们加我微信:DZHningmeng,一起讨论,期待与大家共同成长🥂
前言
这是Spring Boot集成Spring Security的第三章,我将会通过这个系列文章,包括完整的项目搭建、编码过程实现(认证和授权),关键点讲解,来帮助大家了解Spring Security在实际项目中的应用
今天这篇内容主要是登录接口和使用用户的认证
往期系列
Spring Boot集成Spring Security系列一之搭建项目
Spring Boot集成Spring Security系列二之查询用户
功能实现
Spring Security有内置的一套登录逻辑,我们需要重写对应的方法实现自定义的登录逻辑
重写WebSecurityConfigurerAdapter内置的方法
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurity extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Autowired
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
@Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/user/login", "/user/register") // 对登录注册要允许匿名访问
.permitAll()
.antMatchers(HttpMethod.OPTIONS) // 跨域请求会有一次OPTIONS请求
.permitAll()
.antMatchers("/**") // 测试验证,上线需要关闭
.permitAll()
.anyRequest() // 除上面的所有请求全部需要鉴权认证
.authenticated();
// 禁用缓存
httpSecurity.headers().cacheControl();
// 添加JWT filter
httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
// 添加自定义未授权和未登录结果返回
httpSecurity.exceptionHandling()
.accessDeniedHandler(restfulAccessDeniedHandler)
.authenticationEntryPoint(restAuthenticationEntryPoint);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
return new JwtAuthenticationTokenFilter();
}
@Bean
public UserDetailsService userDetailsService() {
// 获取登录用户信息
return username -> {
User admin = userService.queryUserByUsername(username);
if (admin != null) {
List<UserPermission> permissionList = new ArrayList<>();
return new AdminUserDetails(admin, permissionList);
}
throw new UsernameNotFoundException("用户名或密码错误");
};
}
}
这个时候我们再次重新启动项目会发现没有生成密码,然后访问http://localhost:8080/user/queryUserByUsername?username=admin
发现不需要登录就能访问,这是因为我们打开了测试的代码,要注释掉这段代码
.antMatchers("/**") // 测试验证,上线需要关闭
.permitAll()
然后再再次重新启动项目访问接口会报401,这是因为我们没有从登录的生成jwt中获取对应的username,查询到对应权限,所以我们需要写一个登录接口生成jwt返回给前端,然后和一个获取权限的service,才能查询到用户是否有对应接口的权限
编写登录接口
用户校验用户登录生成jwt返回前端的功能
Controller层
@RequestMapping(value = "/login", method = RequestMethod.POST)
public CommonResult login(@RequestBody UserLoginParam userLoginParam, HttpServletResponse response) {
String token = userService.login(userLoginParam.getUsername(), userLoginParam.getPassword());
if(token == null) {
return CommonResult.validateFailed("用户名或者密码错误");
}
Map<String, String> tokenMap = new HashMap<>();
tokenMap.put("tokenHeader", tokenHeader);
tokenMap.put("token", token);
response.setHeader(tokenHeader, token);
return CommonResult.success(tokenMap);
}
ServiceImpl层
@Override
public String login(String username, String password) {
String token = null;
try {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if(!passwordEncoder.matches(password, userDetails.getPassword())){
throw new BadCredentialsException("密码不正确");
}
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
token = jwtTokenUtil.generateToken(userDetails);
} catch (Exception e) {
LOGGER.warn("登录异常: {}", e.getMessage());
}
return token;
}
这个时候启动会报循环依赖的问题,因为SpringSecurity和userServiceImpl循环依赖了,使用需要在配置文件加一个允许Spring循环依赖的配置(上面两个的不会导致其他问题,其他情况需要具体分析会不会引起其他问题)
spring:
main:
allow-circular-references: true
再次启动访问接口,这次我们使用IDEA内置的请求工具,增加一个api.http文件
登录接口,查看返回值,可以查看登录成功
接下来测试之前开发的查询用户接口Authorization先不加看什么情况,可以看到认证表不通过
然后加上Authorization: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE2NTAxMzI5MzkwNzgsImV4cCI6MTY1MDczNzczOX0.pB0OMZJg11NqOWSi72G-XY-fN-el9bWS3bxDKyM07bytPXPVaVd-VC4IMYKmoPfO8vkFoe5l-ThhTSnvxYNCCg(上一次请求生成的)
可以看到接口的请求成功,这样子登录的功能和接口的token解析就完成了,下一篇我们会讲到如果鉴权,就是对接口进行权限管理。
🚴♀️ 结束语
通过项目实践,结合具体的步骤,输出相关的文章,给小伙伴一些有些的知识点,希望大家喜欢我的文章,希望认识到更多志同道合的伙伴,如果你也对技术感兴趣,可以加我好友,互相探讨,一起进步
Github: Cheering-baby
公众号: 程序员路飞
vx: DZHningmeng
最后,如果喜欢我的文章,可以给个赞👍或者关注➕都是对我最大的支持