# springboot-security-demo-v1 **Repository Path**: lierha/springboot-security-demo-v1 ## Basic Information - **Project Name**: springboot-security-demo-v1 - **Description**: SpringSecurity基于内存的安全认证,适合初学者 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2021-03-17 - **Last Updated**: 2021-11-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SpringSecurity入门 本项目是基于内存认证,供初学者学习,基于数据库认证可参考spring-boot-security-demo-v2 ## 小试牛刀 添加依赖 ```xml org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-web ``` 准备一个访问接口 ```java package com.security.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @GetMapping("/hello") public String hello() { return "hello"; } } ``` 配置一个用户 ```properties spring.security.user.name=guest spring.security.user.password=123456 spring.security.user.roles=user ``` 启动项目访问:http://localhost:8080/hello 项目自动跳转到由spring security提供的页面 输入账号密码后,再次访问http://localhost:8080/hello ## 基于内存的认证 ```java package com.security.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean PasswordEncoder passwordEncoder(){ //密码加密,使用NoOpPasswordEncoder即不加密 return NoOpPasswordEncoder.getInstance(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //root有ADMIN和DBA角色,admin有ADMIN和USER角色,cc有USER角色 auth.inMemoryAuthentication() .withUser("root").password("123").roles("ADMIN","DBA") .and() .withUser("admin").password("123").roles("ADMIN","USER") .and() .withUser("user").password("123").roles("USER"); } @Override protected void configure(HttpSecurity http) throws Exception { //url为/admin/的需要admin角色,/user/的需要admin或者user,/db/的需要admin和dba角色 http.authorizeRequests() .antMatchers("/admin/**") .hasRole("ADMIN") .antMatchers("/user/**") .access("hasAnyRole('ADMIN','USER')") .antMatchers("/db/**") .access("hasAnyRole('ADMIN') and hasRole('DBA')") .anyRequest() .authenticated() .and() .formLogin().permitAll()//允许登录 .and() .csrf().disable();//关闭跨域 } } ``` ## 路径拦截 ```java package com.security.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @GetMapping("/hello") public String hello() { return "hello"; } @GetMapping("/admin/hello") public String hello2(){ return "admin"; } @GetMapping("/db/hello") public String hello3(){ return "db"; } @GetMapping("/user/hello") public String hello4(){ return "user"; } } ``` 说明:访问以上路径会跳转到登录页面,使用不同角色的账号登陆后,再次访问各自允许的url路径会成功,而不允许的会失败。 ## 方法拦截 除了url路径拦截校验,security还支持方法注解拦截 首先添加配置支持 ```java @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true) //prePostEnabled=true会解锁@PreAuthorize和@PostAuthorize两个注解 //@PreAuthorize注解会在方法执行前进行验证,而@PostAuthorize 注解在方法执行后进行验证。 //securedEnabled=true会解锁@Secured注解。 public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter { ] ``` 添加方法 ```java package com.security.service; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; @Service public class MethodService { @Secured("ROLE_ADMIN")//访问此方法需要ADMIN角色 public String admin() {return "hello admin";} @PreAuthorize("hasRole('ADMIN') and hasRole('DBA')") //访问此方法需要ADMIN且DBA public String dba() { return "hello dba"; } @PreAuthorize("hasAnyRole('ADMIN','DBA','USER')") //三个都行 public String user() { return "hello user"; } } ``` 改造HelloController中hello方法,调用MethodService中添加了拦截的方法 ```java package com.security.controller; import com.security.service.MethodService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @Autowired MethodService methodService; @GetMapping("/hello") public String hello() { return methodService.admin(); } } ``` 可以发现,之前hello路径,任何角色登陆后都可以访问,改造后需要admin角色才行。当然controller方法上直接使用该类注解也是可以的 ## 自定义登录页面及成功失败处理 ```java package com.security.config; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.*; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; @Configuration public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean PasswordEncoder passwordEncoder(){ //密码加密,使用NoOpPasswordEncoder即不加密 return NoOpPasswordEncoder.getInstance(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //root有ADMIN和DBA角色,admin有ADMIN和USER角色,cc有USER角色 auth.inMemoryAuthentication() .withUser("root").password("123").roles("ADMIN","DBA") .and() .withUser("admin").password("123").roles("ADMIN","USER") .and() .withUser("user").password("123").roles("USER"); } @Override protected void configure(HttpSecurity http) throws Exception { //url为/admin/的需要admin角色,/user/的需要admin或者user,/db/的需要admin和dba角色 http.authorizeRequests() .antMatchers("/admin/**") .hasRole("ADMIN") .antMatchers("/user/**") .access("hasAnyRole('ADMIN','USER')") .antMatchers("/db/**") .access("hasAnyRole('ADMIN') and hasRole('DBA')") .anyRequest() .authenticated() .and() .formLogin().permitAll() .loginPage("/login.html") //自定义登陆页面 .loginProcessingUrl("/mylogin")//自定义登陆页面的登陆action .successHandler(new AuthenticationSuccessHandler() {//登陆成功后 @Override public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication auth) throws IOException { Object principal = auth.getPrincipal(); resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); resp.setStatus(200); Map map = new HashMap<>(); map.put("status", 200); map.put("msg", principal); ObjectMapper om = new ObjectMapper(); out.write(om.writeValueAsString(map)); out.flush(); out.close(); } }) .failureHandler(new AuthenticationFailureHandler() {//登陆失败后 @Override public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException { resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); resp.setStatus(401); Map map = new HashMap<>(); map.put("status", 401); if (e instanceof LockedException) { map.put("msg", "账户被锁定,登录失败!"); } else if (e instanceof BadCredentialsException) { map.put("msg", "账户名或密码输入错误,登录失败!"); } else if (e instanceof DisabledException) { map.put("msg", "账户被禁用,登录失败!"); } else if (e instanceof AccountExpiredException) { map.put("msg", "账户已过期,登录失败!"); } else if (e instanceof CredentialsExpiredException) { map.put("msg", "密码已过期,登录失败!"); } else { map.put("msg", "登录失败!"); } ObjectMapper om = new ObjectMapper(); out.write(om.writeValueAsString(map)); out.flush(); out.close(); } }) .and() .logout()//开启注销登陆 .logoutUrl("/logout")//注销登陆请求url .clearAuthentication(true)//清除身份信息 .invalidateHttpSession(true)//session失效 .addLogoutHandler(new LogoutHandler() {//注销处理 @Override public void logout(HttpServletRequest req, HttpServletResponse resp, Authentication auth) { } }) .logoutSuccessHandler(new LogoutSuccessHandler() { //注销成功处理 @Override public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication auth) throws IOException { resp.sendRedirect("/login.html");//跳转到自定义登陆页面 } }) .and() .csrf().disable(); } } ```