# spring-boot-security **Repository Path**: Endy/spring-boot-security ## Basic Information - **Project Name**: spring-boot-security - **Description**: No description available - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2018-10-27 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### 1. 添加Spring Security依赖 ```xml org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-thymeleaf org.thymeleaf.extras thymeleaf-extras-springsecurity4 ``` ### 2. 添加Spring Security配置,该类继承于WebSecurityConfigurerAdapter,重写方法: ```java protected void configure(HttpSecurity http) throws Exception {} protected void configure(AuthenticationManagerBuilder auth) throws Exception {} //Security版本不同,函数名也不同 ``` 需要添加 **@Configuration** 和 **@EnableWebSecurity** 注解 ```java @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true)//启用Security注解,例如最常用的@PreAuthorize public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean public ICustomUserService customUserService(){ return new CustomUserServiceImpl(); } @Bean public CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler(){ return new CustomAuthenticationSuccessHandler(); } @Bean public CustomAuthenticationFailureHandler customAuthenticationFailureHandler(){ return new CustomAuthenticationFailureHandler(); } @Bean public CustomLogoutSuccessHandler customLogoutSuccessHandler(){ return new CustomLogoutSuccessHandler(); } @Bean public SessionRegistry sessionRegistry(){ return new SessionRegistryImpl(); } // Web层面的配置,一般用来配置无需安全检查的路径 @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/js/**", "/css/**", "/images/**", "/**/favicon.ico"); } // 身份验证配置,用于注入自定义身份验证Bean和密码校验规则 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //在内存中模拟保存user对象 // auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN", "USER"); // auth.inMemoryAuthentication().withUser("root").password("root").roles("ADMIN"); // auth.inMemoryAuthentication().withUser("amy").password("amy").roles("USER"); //使用UserDetailsService获取数据库用户对象,可以指定加密器 auth.userDetailsService(customUserService());//.passwordEncoder(new BCryptPasswordEncoder()); } // Request层面的配置 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/static/**").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login")//GET请求,登录页面的url为/login,不能直接访问templates下的html文件(登录请求也是/login,但是是POST请求) .usernameParameter("username")//登录页面用户名使用name=username,默认即为username,所以可以不配该行 .passwordParameter("password")//登录页面密码使用name=password,默认即为password,所以可以不配该行 //.successForwardUrl("/index")//默认登录成功后跳转到/下,修改默认的登录成功后跳转的url .successHandler(customAuthenticationSuccessHandler())//可以在用户成功登录后获取用户的一些用户信息,或者获取用户登录时间点以便存于数据库 //.failureUrl("/login?error")//认证失败后直接回到登录页面,并携带参数error .failureHandler(customAuthenticationFailureHandler())//认证失败后的处理,可以获取失败的原因 .permitAll()//如果使用loginPage("xxx")配置自定义的登录url,则必须指定认证要求 .and() .logout() //.logoutUrl("/logout")//触发注销操作的url,默认为/logout //.logoutSuccessUrl("/login")//注销操作发生后重定向到的url,默认为 /login?logout。如果指定了logoutSuccessHandler,则会忽略logoutSuccessUrl .logoutSuccessHandler(customLogoutSuccessHandler())//获取用户注销时间点存于数据库等功能 .invalidateHttpSession(true)//注销后销毁session,默认为true .permitAll() .and() .httpBasic(); //.and() //配置Session管理器,用于监听session,需要注意点:(据测试,第1点貌似不是必须的) //1、编写一个继承AbstractSecurityWebApplicationInitializer的子类,重写enableHttpSessionEventPublisher方法,使其返回true //2、使用ServletListenerRegistrationBean,注册一个Listener(在WebMvcConfig配置类里) //3、SpringSecurity是通过管理UserDetails对象来实现用户管理的,而类比较不能用==来比较,需要使用equals来比较,所以重写UserDetails子类的equals和hasCode方法(使用username作为唯一凭据) //用户每次登录都会调用ConcurrentSessionControlAuthenticationStrategy的onAuthentication方法,判断session是否有限制 http .sessionManagement()//进行session管理 .maximumSessions(1)//同一时刻,每个帐号只能有一个登录,默认为-1(无限制) .sessionRegistry(sessionRegistry())//用于Session管理,记录每一次的Session值 .maxSessionsPreventsLogin(false)//true表示阻止后面的登录,false表示后面的登录会使前面登录成功的session失效,该值会影响ConcurrentSessionControlAuthenticationStrategy类的exceptionIfMaximumExceeded属性 //假设将maxSessionsPreventsLogin设为true,则会存在一个问题,如果异常退出或者系统关闭,则下一次再登录的时候会导致登入不了 .expiredUrl("/login");//session过期或者失效后回到指定url } } ``` ### 3.创建UserDetailsService的实现类,重写loadUserByUsername方法 ```java @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser sysUser = sysUserRepository.findByUsername(username); if(sysUser == null){ throw new UsernameNotFoundException("用户名不存在!"); } return sysUser; } ``` ### 4.配置handler类(按需求配置) #### 4.1、用于登录成功后的handler ```java @Component public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Autowired private ICustomUserService customUserService; @Autowired private SessionRegistry sessionRegistry; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println(sessionRegistry); List authorities = (List) authentication.getAuthorities(); authorities.stream().forEach(authority -> System.out.println(authority.getAuthority())); UserDetails userDetails = (UserDetails) authentication.getPrincipal(); System.out.println("用户名:" + userDetails.getUsername()); System.out.println("密码:" + userDetails.getPassword()); String ip = request.getRemoteAddr(); String uri = request.getRequestURI(); System.out.println("IP地址:" + ip); System.out.println("uri:" + uri); //更新用户登录时间 customUserService.updateSysUserLoginTime(new Date(), userDetails.getUsername()); request.getSession().setAttribute("user", userDetails); //List allSessions = sessionRegistry.getAllSessions(authentication.getPrincipal(), false); //转发到/index request.getRequestDispatcher("/index").forward(request, response); } } ``` #### 4.2、用于登录失败的handler ```java @Component public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { System.out.println("认证失败原因:" + exception); response.sendRedirect("/login?error"); } } ``` #### 4.3、用于注销的handler ```java @Component public class CustomLogoutSuccessHandler implements LogoutSuccessHandler { @Autowired private ICustomUserService customUserService; @Autowired private SessionRegistry sessionRegistry; @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { //记录登出时间 customUserService.updateSysUserLogoutTime(new Date(), ((UserDetails)authentication.getPrincipal()).getUsername()); HttpSession session = request.getSession(); session.removeAttribute("user"); session.invalidate(); //List allSessions = sessionRegistry.getAllSessions(authentication.getPrincipal(), false); response.sendRedirect("/login");//重新回到登录页面 } } ``` ### 5.Session管理 #### 5.1、编写一个继承AbstractSecurityWebApplicationInitializer的子类,重写enableHttpSessionEventPublisher方法,使其返回true ```java @Configuration public class SpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer { /** * Override this if {@link HttpSessionEventPublisher} should be added as a listener. * This should be true, if session management has specified a maximum number of * sessions. * * @return true to add {@link HttpSessionEventPublisher}, else false */ @Override protected boolean enableHttpSessionEventPublisher() { return true; } } ``` #### 5.2、在webmvc配置文件中注册HttpSessionEventPublisher监听器,使用ServletListenerRegistrationBean ```java //配置org.springframework.security.web.session.HttpSessionEventPublisher监听器 @Bean public ServletListenerRegistrationBean sessionListener(){ return new ServletListenerRegistrationBean<>(sessionEventPublisher()); } @Bean public HttpSessionEventPublisher sessionEventPublisher(){ return new HttpSessionEventPublisher(); } ``` #### 5.3、对实现UserDetail接口的子类进行重写equals和hasCode方法 *SpringSecurity是通过管理UserDetails对象来实现用户管理的,而类比较不能用==来比较,需要使用equals来比较,所以重写UserDetails子类的equals和hasCode方法(使用username作为唯一凭据)* ```java //判断用户是否已经登录过,需要重写equals和hashCode方法 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SysUser sysUser = (SysUser) o; return username.equals(sysUser.username); } @Override public int hashCode() { return username.hashCode(); } ``` ### 6.html中使用Security标签(基于thymeleaf模版引擎) ```html
```
------ #### 插入一波广告,欢迎关注 |**#**|**#**| |:--|:--:| |**作者:**|**Cay**| |**QQ:**|点击这里给我发消息| |**邮箱:**|**412425870@qq.com**| |**微信公众号:Cay课堂**|**![](https://github.com/caychen/readme/raw/master/img/%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7.jpg)**| |**csdn博客:**|**[http://blog.csdn.net/caychen](http://blog.csdn.net/caychen "我的csdn博客")**| |**码云:**|**[https://gitee.com/caychen/](https://gitee.com/caychen/ "我的码云")**| |**github:**|**[https://github.com/caychen](https://gitee.com/caychen/ "我的github")**| |**点击群号或者扫描二维码即可加入QQ群:[328243383(1群)](https://jq.qq.com/?_wv=1027&k=54r3suD)**|**![](https://github.com/caychen/readme/raw/master/img/1%E7%BE%A4.png)**| |**点击群号或者扫描二维码即可加入QQ群:[180479701(2群)](https://jq.qq.com/?_wv=1027&k=521g7zY)**|**![](https://github.com/caychen/readme/raw/master/img/2%E7%BE%A4.png)**|