# spring_security_demo **Repository Path**: johndriver/spring_security_demo ## Basic Information - **Project Name**: spring_security_demo - **Description**: Spring Security 的范例 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-06-02 - **Last Updated**: 2021-06-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SpringSecurity ## 基本使用 * 1.maven引入 ```xml org.springframework.boot spring-boot-starter-security ``` * 2.访问 ```text /login ->springSecurity内置登录页面(用户名:user,密码:控制台打印出来的随机密码) ``` ## 自定义页面(登录、登录失败、登录成功页面) ```text /login -> .loginPage("/login.html"):找自定义的login.html页面 -> .antMatchers("/login.html").permitAll():开放/login.html访问 -> .loginProcessingUrl("/login"):指定/login的post请求(login.html的form里边的action值)会走UserDetailsService的登录验证逻辑 -> .successForwardUrl("/toMain") 登录成功跳转方法,通过controller的post方法 -> .failureForwardUrl("/toError") 登录失败跳转方法,通过controller的post方法 ->.antMatchers("/login.html").permitAll() 放性自定义错误页面 ``` * 1.页面 `login.html` `username` `password` 固定写法,不可修改(后面有可修改犯法) ```xml
用户名:
密码:
``` * 2.配置 `SecurityConfig` ```java @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { //表单登录配置 http.formLogin() //当发现/login时认为是登录,必须与表单提交的地址一样,就可以去执行UserDetailsServiceImpl .loginProcessingUrl("/login") //自定义登录页面 .loginPage("/login.html") ; //认证授权 http.authorizeRequests() //login.html error.html不需要认证 .antMatchers("/login.html").permitAll() //其他任何请求都需要认证,必须登录后才可以访问 .anyRequest().authenticated() ; //关闭csrf防护 http.csrf().disable(); } } ``` * 3.自定义登录逻辑 * 3.1 自定义密码加密 `SecurityConfig` 中添加 ```text // 密码加密器 @Bean public PasswordEncoder getPasswordEncoder(){ return new BCryptPasswordEncoder(); } ``` 加密器的方法 ```text passwordEncoder.encode(原始密码):加密原始密码 passwordEncoder.matches(原始密码,加密后的密码):验证原始密码和加密密码是否匹配 passwordEncoder.upgradeEncoding():如果解析的密码能够再次进行解析且达到更 安全的结果则返回 true,否则返回 false。默认返回 false。 ``` * 3.2 自定义 `UserDetailsService` ```java @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { System.out.println("UserDetailsServiceImpl.loadUserByUsername"); //1.查数据库,判断用户名是否存在 //"admin":模拟的数据库查出来的密码 if(!"admin".equals(username)){ throw new UsernameNotFoundException("用户名不存在"); } //2.模拟数据库查询的密码(注册时加密过)解析,或者直接放入构造方法 String password = passwordEncoder.encode("123"); //3.组合返回User对象 //"admin,normal,ROLE_abc":模拟数据库查询到的权限 return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc")); } } ``` * 4.登录成功跳转 * 4.1 `successForwardUrl` SecurityConfig ```text .successForwardUrl("/toMain") ``` controller ```text @PostMapping("/toMain") public String toMain(){ System.out.println("执行登录方法"); return "redirect:main.html"; } ``` main.html * 4.2 自定义登录成功处理器 `AuthenticationSuccessHandler` MyAuthenticationSuccessHandler ```java public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private String url; public MyAuthenticationSuccessHandler(String url) { this.url = url; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { //可以取得用户信息,用于页面 User user = (User) authentication.getPrincipal(); System.out.println(user.getUsername()); System.out.println(user.getPassword()); System.out.println(user.getAuthorities()); response.sendRedirect(url); } } ``` SecurityConfig ```text .successHandler(new MyAuthenticationSuccessHandler("main.html")) ``` * 4.登录失败跳转 * 4.1 failureForwardUrl SecurityConfig ```text .failureForwardUrl("/toError") .antMatchers("/login.html").permitAll() ``` controller ```text @PostMapping("/toError") public String toError(){ System.out.println("执行登录方法"); return "redirect:error.html"; } ``` error.html * 4.2 自定义登录失败处理器 `AuthenticationFailureHandler` MyAuthenticationFailureHandler ```text public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { private final String url; public MyAuthenticationFailureHandler(String url) { this.url = url; } @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { //可以获取异常信息 e.getMessage(); response.sendRedirect(url); } } ``` SecurityConfig ```text .failureHandler(new MyAuthenticationFailureHandler("/error.html")) ``` ## 授权认证配置 ```java @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { //... //认证授权 http.authorizeRequests() //login.html error.html不需要认证 .antMatchers("/error.html").permitAll() .antMatchers("/login.html").permitAll() //放行静态资源 .antMatchers("/js/**","/css/**","/images/**").permitAll() .antMatchers("/**/*.jpg").permitAll() .antMatchers(HttpMethod.GET,"/**/*.jpg").permitAll() //正则方式 .regexMatchers(".+[.]jpg").permitAll() //mvcMatchers //.mvcMatchers("/demo").servletPath("/xxx").permitAll() .antMatchers("/main1.html").hasAuthority("admin") //角色ROLE_rolename在这里去掉ROLE_ .antMatchers("/main1.html").hasRole("rolename") //任何请求 .anyRequest() //都需要认证,必须登录后才可以访问 .authenticated() ; //关闭csrf防护 http.csrf().disable(); } } ``` assess ```text 效果一样 .antMatchers("/error.html").permitAll() .antMatchers("/error.html").access("permitAll") ``` 自定义权限认证 ```text 接口 public interface MyService { boolean hasPermission(HttpServletRequest request, Authentication authentication); } 实现 @Service public class MyServiceImpl implements MyService { @Override public boolean hasPermission(HttpServletRequest request, Authentication authentication) { Object obj = authentication.getPrincipal(); if(obj instanceof UserDetails){ UserDetails details = (UserDetails) obj; Collection authorities = details.getAuthorities(); return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI())); } return false; } } SecurityConfig配置 .anyRequest().access("@myServiceImpl.hasPermission(request,authentication)") ``` 权限 ```text permitAll:允许所有人访问 denyAll:任何人都不允许 anonymous:可以匿名访问(与permitAll不同点:这个会到拦截链中) authenticated:需要认证 fullyauthenticated:(勾选记住我不行,需要一步步登录)需要完全认证 rememgetMe:勾选记住我 ``` 角色权限判断 ```text //权限 hasAuthority(String auth) hasAnyAuthority(String... auth) //角色 hasRole(String role) hasAnyRole(String... role) //ip hasIpAddress() ``` ## 基于注解的访问控制 ### @Secured ```text APP @EnableGlobalMethodSecurity(securedEnabled = true) 方法或类 @Secured("ROLE_abc") @PostMapping("/toMain") public String toMain(){ System.out.println("执行登录方法"); return "redirect:main.html"; } ``` ### @PreAuthorize/@PostAtrhrize PreAuthorize:执行方法前验证权限 ```text app: @EnableGlobalMethodSecurity(prePostEnabled = true) 方法或类 @PreAuthorize("hasRole('abc')")//@PreAuthorize("hasRole('ROLE_abc')")也允许 @PostMapping("/toMain") public String toMain(){ System.out.println("执行登录方法"); return "redirect:main.html"; } ``` @PostAtrhrize执行方法后验证权限 ## 自定义权限受限页面(403页面) * 自定义 `AccessDeniedHandler` ```java @Component public class MyAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException { //设置响应状态码 response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setHeader("Content-Type","application/json;charset=utf-8"); PrintWriter writer = response.getWriter(); writer.write("{\"status\":\"error\",\"msg\":\"权限不足\"}"); writer.flush(); writer.close(); } } ``` 配置SecurityConfig ```text @Autowired private MyAccessDeniedHandler myAccessDeniedHandler; //异常处理 http.exceptionHandling() //访问权限304 .accessDeniedHandler(myAccessDeniedHandler); ``` ## RememberMe功能实现 * 1.pom依赖 ```text mysql mysql-connector-java org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.3 ``` * 2.配置 建立数据库:security ```text spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url= jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Shanghai spring.datasource.username= root spring.datasource.password= root ``` * 3.remenber-me ```xml
用户名:
密码:
记住我:
``` 4. 配置 ```text @Autowired private UserDetailsServiceImpl userDetailsService; @Autowired private DataSource dataSource; @Autowired private PersistentTokenRepository persistentTokenRepository; @Bean PersistentTokenRepository getPersistentTokenRepository(){ JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); //自动建表,第二次启动要注释掉 //jdbcTokenRepository.setCreateTableOnStartup(true); return jdbcTokenRepository; } @Override protected void configure(HttpSecurity http) throws Exception { http.rememberMe() //过期时间(默认两周) .tokenValiditySeconds(60) //.rememberMeParameter() //表单参数 默认remember-me //DetailsService登录逻辑 .userDetailsService(userDetailsService) //存储token的Repository .tokenRepository(persistentTokenRepository) ; } ``` ## thymeleaf(前后端不分离项目) ```text org.thymeleaf.extras thymeleaf-extras-springsecurity5 org.springframework.boot spring-boot-starter-thymeleaf ``` 页面获取信息 ```html Title 账号 123
账号123
凭证123
权限123
客户端地址123
sessionId123
``` 权限判断 ```html



``` ## csrf跨站请求访问 spring security默认开启csrf防护:要求访问带上参数:_csrf=token(服务器产生) ```html
用户名:
密码:
记住我:
``` ```text @RequestMapping("/showLogin") public String showLogin(){ return "login"; } ``` ```text .antMatchers("/showLogin").permitAll() ``` ```text /showLogin ->开放/showLogin ->crolller->login.html获得服务端的生成的_csrf(${_csrf.token}) ->请求时带上ame="_csrf" th:value="${_csrf.token}"->符合csrf防护 ```