1、在springboot基础上添加maven:
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>5.3.1.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>5.3.1.RELEASE</version> </dependency>
2、启动,启动日志:Using generated security password: 9141f795-55b8-4433-ae85-77c47a56e079 用户名是user
3、url请求,重定向到服务认证,输入用户名密码,认证成功后重定向到请求的url。再次访问请求url,不再认证。访问urlhttp://localhost:8399/logout 退出。
4、改密码:
在application.properties添加
spring.security.user.name=yaohuiqin spring.security.user.password=123
@SpringBootApplication /*@EnableAutoConfiguration(exclude = { org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class })*/ public class SpringsecurityApplication { public static void main(String[] args) { SpringApplication.run(SpringsecurityApplication.class, args); } }
再启动项目,则可以用用户名yaohuiqin,密码123登录。
换一种方式:删除application.properties文件中的用户名密码,添加类:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("yhq") .password("123").roles("admin"); } }
换一种方式
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Bean protected UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("wxh").password("123").roles("admin").build()); manager.createUser(User.withUsername("yhquser").password("123").roles("user").build()); return manager; } }
5、表单登录配置
http.formLogin() .loginPage("/login.html") .loginProcessingUrl("/doLogin") .usernameParameter("name") .passwordParameter("passwd") .defaultSuccessUrl("/index") .successForwardUrl("/index") .permitAll() .and() .logout() .logoutUrl("/logout") .logoutRequestMatcher(new AntPathRequestMatcher("/logout","POST")) .logoutSuccessUrl("/index") .deleteCookies() .clearAuthentication(true) .invalidateHttpSession(true) .permitAll();
注销登录的配置:
- 默认注销的 URL 是
/logout
,是一个 GET 请求,我们可以通过 logoutUrl 方法来修改默认的注销 URL。 - logoutRequestMatcher 方法不仅可以修改注销 URL,还可以修改请求方式,实际项目中,这个方法和 logoutUrl 任意设置一个即可。
- logoutSuccessUrl 表示注销成功后要跳转的页面。
- deleteCookies 用来清除 cookie。
- clearAuthentication 和 invalidateHttpSession 分别表示清除认证信息和使 HttpSession 失效,默认可以不用配置,默认就会清除。
6、Spring Security 结合 Jwt 实现无状态登录
6.1 jwt认证和session认证
JWT,全称是 Json Web Token , 是一种 JSON 风格的轻量级的授权和身份认证规范,可实现无状态、分布式的 Web 应用授权:
JWT属于无状态认证,支持集群化部署,服务端可以任意迁移,减少服务端存储session压力,多平台跨域。流程:用户发送用户名和密码,服务端验证成功后用户信息加密并且编码成一个 token给客户端,客户端每次请求携带token,服务端接收请求时会解密token再验证token是否有,效获取用户登录信息,再根据授权获取受保护的资源
有以下几个方法可以做到失效 JWT token(后期再考虑不同方式的优缺点)
- 将 token 存入 DB(如 Redis)中,失效则删除;但增加了一个每次校验时候都要先从 DB 中查询 token 是否存在的步骤,而且违背了 JWT 的无状态原则(这不就和 session 一样了么?)。
- 维护一个 token 黑名单,失效则加入黑名单中。
- 在 JWT 中增加一个版本号字段,失效则改变该版本号。
- 在服务端设置加密的 key 时,为每个用户生成唯一的 key,失效则改变该 key。
7、Spring Security 配置
在前后端分离这样的开发架构下,前后端的交互都是通过 JSON 来进行,无论登录成功还是失败,都不会有什么服务端跳转或者客户端跳转之类。
登录成功了,服务端就返回一段登录成功的提示 JSON 给前端,前端收到之后,该跳转该展示,由前端自己决定,就和后端没有关系了。
登录失败了,服务端就返回一段登录失败的提示 JSON 给前端,前端收到之后,该跳转该展示,由前端自己决定,也和后端没有关系了。
package com.yhq.springsecurity.config; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; 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.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import java.io.PrintWriter; @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { //不同情况下的配置,用来记录 http.authorizeRequests() .antMatchers("/", "/index").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/loginindex") .permitAll() .and() .logout() .permitAll(); http.formLogin() .loginPage("/login.html") .loginProcessingUrl("/doLogin") .usernameParameter("name") .passwordParameter("passwd") .defaultSuccessUrl("/index") .successForwardUrl("/index") .permitAll() .and() .logout() .logoutUrl("/logout") .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "POST")) .logoutSuccessUrl("/index") .deleteCookies() .clearAuthentication(true) .invalidateHttpSession(true) .permitAll(); //统统 JSON 交互 http.formLogin().successHandler((req, resp, authentication) -> { Object principal = authentication.getPrincipal(); resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write(new ObjectMapper().writeValueAsString(principal)); out.flush(); out.close(); }).and().formLogin().failureHandler((req, resp, e) -> { resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write(e.getMessage()); out.flush(); out.close(); }).and().csrf().disable().exceptionHandling() .authenticationEntryPoint((req, resp, authException) -> { resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write("尚未登录,请先登录"); out.flush(); out.close(); }).and().logout() .logoutUrl("/logout") .logoutSuccessHandler((req, resp, authentication) -> { resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write("注销成功"); out.flush(); out.close(); }) .permitAll(); } @Bean @Override public UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build(); PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); UserDetails user2 = User .withUsername("yun") .password(passwordEncoder.encode("123456")) .roles("USER") .build(); return new InMemoryUserDetailsManager(user2); } }