摘要: 原创出处 juejin.cn/post/7026734817853210661 「清茶淡粥酱」欢迎转载,保留摘要,谢谢!
今天来一篇 Spring Security 精讲,相信你看过之后能彻底搞懂 Spring Security。
Spring Security简介
Spring Security 是一种高度自定义的安全框架,利用(基于)SpringIOC/DI和AOP功能,为系统提供了声明式安全访问控制功能,「减少了为系统安全而编写大量重复代码的工作」 。
「核心功能:认证和授权」
Spring Security 认证流程
Spring Security 项目搭建
导入依赖
Spring Security已经被Spring boot进行集成,使用时直接引入启动器即可
<dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > </dependency >
访问页面
导入spring-boot-starter-security启动器后,Spring Security已经生效,默认拦截全部请求,如果用户没有登录,跳转到内置登录页面。
在浏览器输入:http://localhost:8080/
进入Spring Security内置登录页面
用户名:user。
密码:项目启动,打印在控制台中。
自定义用户名和密码
修改**「application.yml」** 文件
# 静态用户,一般只在内部网络认证中使用,如:内部服务器1,访问服务器2 spring: security: user: name: test # 通过配置文件,设置静态用户名 password: test # 配置文件,设置静态登录密码
UserDetailsService详解
什么也没有配置的时候,账号和密码是由Spring Security定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。所以我们要通过**「自定义逻辑控制认证逻辑」**。如果需要自定义逻辑时,只需要实现UserDetailsService接口
@Component public class UserSecurity implements UserDetailsService { @Autowired private UserService userService; @Override public UserDetails loadUserByUsername (String userName) throws UsernameNotFoundException { User user = userService.login(userName); System.out.println(user); if (null ==user){ throw new UsernameNotFoundException("用户名错误" ); } org.springframework.security.core.userdetails.User result = new org.springframework.security.core.userdetails.User( userName,user.getPassword(), AuthorityUtils.createAuthorityList() ); return result; } }
PasswordEncoder密码解析器详解
PasswordEncoder
「PasswordEncoder」 是SpringSecurity 的密码解析器,用户密码校验、加密 。自定义登录逻辑时要求必须给容器注入PaswordEncoder的bean对象
SpringSecurity 定义了很多实现接口**「PasswordEncoder」** 满足我们密码加密、密码校验 使用需求。
PasswordEncoder密码解析器详解
自定义密码解析器
编写类,实现PasswordEncoder 接口
public class MyMD5PasswordEncoder implements PasswordEncoder { @Override public String encode (CharSequence charSequence) { try { MessageDigest digest = MessageDigest.getInstance("MD5" ); return toHexString(digest.digest(charSequence.toString().getBytes())); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); return "" ; } } @Override public boolean matches (CharSequence charSequence, String s) { return s.equals(encode(charSequence)); } private String toHexString (byte [] tmp) { StringBuilder builder = new StringBuilder(); for (byte b :tmp){ String s = Integer.toHexString(b & 0xFF ); if (s.length()==1 ){ builder.append("0" ); } builder.append(s); } return builder.toString(); } }
2.在配置类中指定自定义密码凭证匹配器
@Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder(); }
登录配置
方式一 转发
http.formLogin() .usernameParameter("name" ) .passwordParameter("pswd" ) .loginPage("/toLogin" ) .loginProcessingUrl("/login" ) .failureForwardUrl("/failure" ); .successForwardUrl("/toMain" );
方式二 :重定向
http.formLogin() .usernameParameter("name" ) .passwordParameter("pswd" ) .loginPage("/toLogin" ) .loginProcessingUrl("/login" ) .defaultSuccessUrl("/toMain" ,true ); .failureUrl("/failure" );
方式三:自定义登录处理器
自定义登录失败逻辑处理器
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { private String url; private boolean isRedirect; public MyAuthenticationFailureHandler (String url, boolean isRedirect) { this .url = url; this .isRedirect = isRedirect; } @Override public void onAuthenticationFailure (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { if (isRedirect){ httpServletResponse.sendRedirect(url); }else { httpServletRequest.getRequestDispatcher(url).forward(httpServletRequest,httpServletResponse); } }
自定义登录成功逻辑处理器
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private String url; private boolean isRedirect; public MyAuthenticationSuccessHandler (String url, boolean isRedirect) { this .url = url; this .isRedirect = isRedirect; } @Override public void onAuthenticationSuccess (HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { if (isRedirect){ response.sendRedirect(url); }else { request.getRequestDispatcher(url).forward(request,response); } } http.formLogin() .usernameParameter("name" ) .passwordParameter("pswd" ) .loginPage("/toLogin" ) .loginProcessingUrl("/login" )
登录相关配置类
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserSecurity userSecurity; @Autowired private PersistentTokenRepository persistentTokenRepository; @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder(); } @Override protected void configure (HttpSecurity http) throws Exception { http.formLogin() .loginPage("/toLogin" ) .usernameParameter("name" ) .passwordParameter("pswd" ) .loginProcessingUrl("/login" ) .defaultSuccessUrl("/toMain" ) .failureUrl("/toLogin" ); http.authorizeRequests() .antMatchers("/toLogin" , "/register" , "/login" , "/favicon.ico" ).permitAll() .antMatchers("/**/*.js" ).permitAll() .regexMatchers(".*[.]css" ).permitAll() .anyRequest().authenticated(); http.logout() .invalidateHttpSession(true ) .clearAuthentication(true ) .logoutSuccessUrl("/" ) .logoutUrl("/logout" ); http.csrf().disable(); } @Bean public PersistentTokenRepository persistentTokenRepository (DataSource dataSource) { JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); return jdbcTokenRepository; } }
角色权限
❝
「hasAuthority(String)」 判断角色是否具有特定权限
❞
http.authorizeRequests().antMatchers("/main1.html" ).hasAuthority("admin" )
❝
「hasAnyAuthority(String ...)」 如果用户具备给定权限中某一个,就允许访问
❞
http.authorizeRequests().antMatchers("/admin/read" ).hasAnyAuthority("xxx" ,"xxx" )
❝
「hasRole(String)」 如果用户具备给定角色就允许访问。否则出现403
❞
http.authorizeRequests().antMatchers("/admin/read" ).hasRole("管理员" )
❝
「hasAnyRole(String ...)」 如果用户具备给定角色的任意一个,就允许被访问
❞
http.authorizeRequests().antMatchers("/guest/read" ).hasAnyRole("管理员" , "访客" )
❝
「hasIpAddress(String)」 请求是指定的IP就运行访问
❞
http.authorizeRequests().antMatchers("/ip" ).hasIpAddress("127.0.0.1" )
403 权限不足页面处理
1.编写类实现接口**「AccessDeniedHandler」**
@Component public class MyAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle (HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException { response.setStatus(HttpServletResponse.SC_OK); response.setContentType("text/html;charset=UTF-8" ); response.getWriter().write( "<html>" + "<body>" + "<div style='width:800px;text-align:center;margin:auto;font-size:24px'>" + "权限不足,请联系管理员" + "</div>" + "</body>" + "</html>" ); response.getWriter().flush(); } }
2.配置类中配置exceptionHandling
http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);/
RememberMe(记住我)
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.rememberMe() .rememberMeParameter("remember-me" ) .tokenValiditySeconds(14 *24 *60 *60 ) .rememberMeCookieName("remember-me" ) .tokenRepository(persistentTokenRepository) .userDetailsService(userSecurity); } @Bean public PersistentTokenRepository persistentTokenRepository (DataSource dataSource) { JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); return jdbcTokenRepository; } }
Spring Security 注解
@Secured
❝
角色校验 ,请求到来访问控制单元方法时必须包含XX角色才能访问
角色必须添加ROLE_前缀
❞
@Secured ({"ROLE_管理员" ,"ROLE_访客" })@RequestMapping ("/toMain" )public String toMain () { return "main" ; }
使用注解@Secured需要在配置类中添加注解 使@Secured注解生效
@EnableGlobalMethodSecurity (securedEnabled = true )
@PreAuthorize
❝
权限检验,请求到来访问控制单元之前必须包含xx权限才能访问,控制单元方法执行前进行角色校验
❞
@PreAuthorize ("hasAnyRole('ROLE_管理员','ROLE_访客')" ) @RequestMapping ("/toMain" ) @PreAuthorize ("hasAuthority('admin:write')" ) public String toMain () { return "main" ; }
使用@PreAuthorize
和@PostAuthorize
需要在配置类中配置注解@EnableGlobalMethodSecurity 才能生效
@EnableGlobalMethodSecurity (prePostEnabled = true )
@PostAuthorize
❝
权限检验,请求到来访问控制单元之后必须包含xx权限才能访问 ,控制单元方法执行完后进行角色校验
❞
@PostAuthorize ("hasRole('ROLE_管理员')" ) @RequestMapping ("/toMain" ) @PreAuthorize ("hasAuthority('admin:write')" ) public String toMain () { return "main" ; }
Spring Security 整合Thymeleaf 进行权限校验
<dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-thymeleaf</artifactId > </dependency > <dependency > <groupId > org.thymeleaf.extras</groupId > <artifactId > thymeleaf-extras-springsecurity5</artifactId > </dependency >
Spring Security中CSRF
什么是CSRF?
CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack” 或者Session Riding。通过伪造用户请求访问受信任站点的非法请求访问。
跨域:只要网络协议,ip地址,端口中任何一个不相同就是跨域请求。
客户端与服务进行交互时,由于http协议本身是无状态协议,所以引入了cookie进行记录客户端身份。在cookie中会存放session id用来识别客户端身份的。在跨域的情况下,session id可能被第三方恶意劫持,通过这个session id向服务端发起请求时,服务端会认为这个请求是合法的,可能发生很多意想不到的事情。
通俗解释:
CSRF就是别的网站非法获取我们网站Cookie值,我们项目服务器是无法区分到底是不是我们的客户端,只有请求中有Cookie,认为是自己的客户端,所以这个时候就出现了CSRF。