MENU

SpringBoot安全管理

February 4, 2020 • Read: 55 • 程序笔记

买了一本@江南一点雨 大佬写的SpringBoot开发的书,开始入坑SpringBoot这个坑。
IMG_2206.JPG
下面写的内容都是书里面提到的,我记一下笔记

Spring Security的基本配置

1. 创建项目,添加依赖

创建一个SpringBoot Web项目,然后添加依赖

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2. 添加接口

在项目中添加一个简单的接口

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(){
        return "Hello Spring Security!";
    }
}

3. 启动项目测试

启动项目后,访问/hello接口会自动跳转到登录界面,如图所示
220330_f01fc82f_3048956.png
默认的登录用户名是user,密码在控制台中会随机生成。

4. 配置用户名和密码

默认的用户名和密码都是可以修改的,直接在配置文件中配置

spring.security.user.name=user
spring.security.user.password=123
spring.security.user.roles=admin

此时登录成功用户还有一个角色是admin

5. 基于内存的认证

在Spring Security中,开发者也可以自定义类继承自WebSecurityConfigurerAdapter,从而实现对Spring Security更多的自定义配置,例如内存的认证:

@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder(){
//        这里的密码加密方式选择不加密
        return NoOpPasswordEncoder.getInstance();
    }
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{
        auth.inMemoryAuthentication()
                .withUser("admin").password("123").roles("ADMIN","USER")
                .and()
                .withUser("user").password("456").roles("USER");
    }
}

在基于内存的用户配置角色时不需要添加ROLE_前缀

6. HttpSecurity

虽然现在实现认证功能,但是受保护的资源都是默认的,也不能根据实际情况进行角色管理,如果要实现这些功能,就需要重写WebSecurityConfigurerAdapter中的另一个方法

@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder(){
//        这里的密码加密方式选择不加密
        return NoOpPasswordEncoder.getInstance();
    }
//    首先配置了三个用户,root用户拥有admin和dba权限,admin用户拥有admin和user权限,user用户拥有user权限
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{
        auth.inMemoryAuthentication()
                .withUser("root").password("root").roles("ADMIN","DBA")
                .and()
                .withUser("admin").password("123").roles("ADMIN","USER")
                .and()
                .withUser("user").password("456").roles("USER");
    }
//    配置HttpSecurity
    protected void configure(HttpSecurity http) throws Exception{
//        开始调用authorizeRequests()方法开启HttpSecurity的配置
        http.authorizeRequests()
//                开始配置各角色访问什么格式的URL必须具备的角色
                .antMatchers("/admin/**")
                .hasRole("ADMIN")
                .antMatchers("/user/**")
                .access("hasAnyRole('ADMIN','USER')")
                .antMatchers("/db/**")
                .access("hasRole('ADMIN') and hasRole('DBA')")
//                表示除了前面定义的URL模式外,用户访问其他的URL都必须认证后访问
                .anyRequest()
                .authenticated()
//                表示开启表单登录
                .and()
                .formLogin()
//                配置了登录接口为“/login”,可以直接调用login接口,发起一个post请求,参数中用户名必须为username,密码必须为password
                .loginProcessingUrl("/login")
//                最后还配置了permitAll,表示相关接口都不需要认证即可访问
                .permitAll()
                .and()
//                关闭csrf
                .csrf()
                .disable();
    }
}

7. 登录表单详细配置

放上我改好之后的参数,这里我登录后是返回json数据,当然也可以跳转页面,这里我就介绍一种,传json的方法

@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder(){
//        这里的密码加密方式选择不加密
        return NoOpPasswordEncoder.getInstance();
    }
//    首先配置了三个用户,root用户拥有admin和dba权限,admin用户拥有admin和user权限,user用户拥有user权限
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{
        auth.inMemoryAuthentication()
                .withUser("root").password("root").roles("ADMIN","DBA")
                .and()
                .withUser("admin").password("123").roles("ADMIN","USER")
                .and()
                .withUser("user").password("456").roles("USER");
    }
//    配置HttpSecurity
    protected void configure(HttpSecurity http) throws Exception{
//        开始调用authorizeRequests()方法开启HttpSecurity的配置
        http.authorizeRequests()
//                开始配置各角色访问什么格式的URL必须具备的角色
                .antMatchers("/admin/**")
                .hasRole("ADMIN")
                .antMatchers("/user/**")
                .access("hasAnyRole('ADMIN','USER')")
                .antMatchers("/db/**")
                .access("hasRole('ADMIN') and hasRole('DBA')")
//                表示除了前面定义的URL模式外,用户访问其他的URL都必须认证后访问
                .anyRequest()
                .authenticated()
//                表示开启表单登录
                .and()
                .formLogin()
//                配置了登录页面,如果用户未授权就会跳转到这里
                .loginPage("/login_page")
//                配置了登录接口为“/login”,可以直接调用login接口,发起一个post请求,参数中用户名必须为username,密码必须为password
                .loginProcessingUrl("/login")
//                定义了认证需要的用户名和密码参数名
                .usernameParameter("username")
                .passwordParameter("password")
//                定义了登录成功的处理逻辑,用户登录成功后可以跳转到某一个页面也可以返回一段成功的JSON
                .successHandler(new AuthenticationSuccessHandler() {
//                    Authentication auth参数用来获取当前用户的信息,登录成功后可以获取当前登录用户的信息给客户端
                    @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<String,Object> map = new HashMap<>();
                        map.put("status",200);
                        map.put("data",principal);
                        map.put("msg","登录成功");
                        ObjectMapper om = new ObjectMapper();
                        out.write(om.writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
//                定义了登录失败的处理逻辑,登录失败的参数里有一个AuthenticationException参数,通过这个参数可以知道登录失败的原因,有一个明确的提示
                .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<String,Object> map = new HashMap<>();
                        map.put("status",401);
                        map.put("data",null);
                        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")
                .clearAuthentication(true)
                .invalidateHttpSession(true)
                .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.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        resp.setStatus(200);
                        Map<String,Object> map = new HashMap<>();
                        map.put("status",200);
                        map.put("data",null);
                        map.put("msg","注销用户成功!");
                        ObjectMapper om = new ObjectMapper();
                        out.write(om.writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
//        最后还配置了permitAll,表示相关接口都不需要认证即可访问
                .permitAll()
                .and()
//                关闭csrf
                .csrf()
                .disable();
    }
}
Archives QR Code Tip
QR Code for this page
Tipping QR Code