重点标识
Json登录。
Security默认是key-value的形式的。
前后端分离,Json处理比较方便。
Security默认是通过request.getpartmater这种方式获取,所以不支持。
自定义登录接口(方法一)
上一篇已经说过了,Spring容器里面是没有AuhtenticationManager,但是有AuhtenticationManagerBuilder。
创建一个登录接口:
@Controller
public class LoginController {
@Autowired
AuthenticationManager authenticationManager;
@PostMapping("/login")
@ResponseBody
public String login(@RequestBody User user, HttpSession session){
//未认证令牌,参考UsernamePasswordAuthenticationFilter这个来写就行了
try {
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(user.getUsername(), user.getPassword());
Authentication auth = authenticationManager.authenticate(authRequest);
//这里还有一点要注意,我们一般都是从SecurityContextHolder取到用户信息的,那这里也别忘了给他放进去
SecurityContextHolder.getContext().setAuthentication(auth);
//还有一点要注意,新版的Security是从session中获取的,那本次会话的下次请求想要拿到,也需要丢到session中去
session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,SecurityContextHolder.getContext());
return auth.getName();
} catch (AuthenticationException e) {
//throw new RuntimeException(e);
//如果出现错误,这里简单点,就不封装了,直接返回好了
return e.getMessage();
}
}
}
向Spring容器中注入一个AuthenticationManager 。
@Configuration
public class SecurityConfig {
UserDetailsService userDetailsService(){
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
inMemoryUserDetailsManager.createUser(User.withUsername("admin").password("{noop}123").build());
return inMemoryUserDetailsManager;
}
@Bean
AuthenticationManager authenticationManager(){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService());
ProviderManager providerManager = new ProviderManager(daoAuthenticationProvider);
return providerManager;
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(a->a.requestMatchers("/login").permitAll().anyRequest().authenticated())
.csrf(c->c.disable());
return http.build();
}
}
User类也给大家
public class User {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
看一下测试结果:
再看一下,同一个会话中,另一个请求能不能获取到用户信息:
ok ,都没问题,实际上,像我们之前写的验证码,如果不想采用过滤器的形式,也可以通过自定义登录这种方式,写在authenticate之前,try里面,这样验证错误被捕捉,也可以直接返回。
修改过滤器UsernamePasswordAuthenticationFilter(方法二)
准备一个过滤器,继承自UsernamePasswordAuthenticationFilter,在它的基础上,改一改就行了。
public class JsonFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String contentType = request.getContentType();
if (contentType.equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE) || contentType.equalsIgnoreCase(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
//json请求
String username = null;
String password = null;
try {
User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
username = user.getUsername();
username = username != null ? username.trim() : "";
password = user.getPassword();
password = password != null ? password : "";
} catch (IOException e) {
throw new RuntimeException(e);
}
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
} else {
//key -value
return super.attemptAuthentication(request, response);
}
}
}
}
然后,配置一下。
@Configuration
public class SecurityConfig {
@Bean
JsonFilter jsonFilter(){
JsonFilter jsonFilter = new JsonFilter();
jsonFilter.setFilterProcessesUrl("/login");
jsonFilter.setAuthenticationSuccessHandler(((request, response, authentication) -> {
response.getWriter().write(authentication.getName());
}));
jsonFilter.setAuthenticationFailureHandler(((request, response, exception) -> {
response.setContentType("text/html;charset=utf-8");
response.getWriter().write(exception.getMessage());
}));
jsonFilter.setAuthenticationManager(authenticationManager());
//配置Security的存储策略,如果我们没有定义jsonFilter,系统会给UsernamePasswordAuthenticationFilter设置SecurityContextRepository
//这个说白了,就是,同一个会话的后续请求认证也要存上
jsonFilter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());
return jsonFilter;
}
UserDetailsService userDetailsService(){
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
inMemoryUserDetailsManager.createUser(User.withUsername("admin").password("{noop}123").build());
return inMemoryUserDetailsManager;
}
@Bean
AuthenticationManager authenticationManager(){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService());
ProviderManager providerManager = new ProviderManager(daoAuthenticationProvider);
return providerManager;
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(a->a.anyRequest().authenticated())
//.addFilter() 添加一个过滤器,但是自动排序,一般不用这个
//添加一个过滤器,在某一个过滤器前面
.addFilterBefore(jsonFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf(c->c.disable());
return http.build();
}
}
这样就ok了,同理,之前的验证码,也可以在这个过滤器里面实现的。
结语
生命不息,奋斗不止,加油!