Spring Security 学习笔记三

135 阅读3分钟

自定义登录

我们之前一直使用的是Spring Security给我们默认的登录页面,不好看不说,还不能扩展。它还在每次测试功能时在那转很久,所以我们需要一个自定义的登录页面。

1.首先我们的登录页面,form有满足四个条件

  • 它是一个post请求
  • 请求路径是/login
  • 姓名的name属性叫username
  • 密码的name属性叫password

在resources下的static创建login.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Login Page</title>
    <style>
        body {
            background-color: #F5F5F5;
            font-family: Arial, Helvetica, sans-serif;
        }
​
        #login-form {
            background-color: #FFFFFF;
            max-width: 480px;
            margin: 0 auto;
            padding: 30px;
            border-radius: 5px;
            box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
        }
​
        h1 {
            text-align: center;
            color: #333333;
        }
​
        input[type="text"], input[type="password"] {
            display: block;
            width: 100%;
            padding: 10px;
            margin-bottom: 20px;
            border-radius: 3px;
            border: none;
            box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
        }
​
        input[type="submit"] {
            display: block;
            width: 100%;
            background-color: #4CAF50;
            color: #FFFFFF;
            padding: 10px;
            font-size: 16px;
            border: none;
            border-radius: 3px;
            cursor: pointer;
        }
​
        input[type="submit"]:hover {
            background-color: #3E8E41;
        }
    </style>
    <script src="./js/index.js"></script>
</head>
<body>
<form id="login-form" action="/toLogin" method="post">
    <h1>登录页面</h1>
    <label>用户:</label>
    <input type="text" placeholder="请输入账户" name="username" required>
​
    <label>密码:</label>
    <input type="password" placeholder="请输入密码" name="password" required>
    <input type="checkbox" name="remember-me" id="remember-me">记住密码
    <input type="submit" value="登录">
</form>
</body>
</html>

2.在配置类中做配置

@Autowired
private MySuccessHandler mySuccessHandler;
@Autowired
private MyFailureHandler myFailureHandler;
​
@Bean
public SecurityFilterChain chain(HttpSecurity http) throws Exception {
    return http
           .csrf().disable()
           .authorizeRequests()
           .anyRequest().authenticated()
           .and()
        //*
           .formLogin()//首先,我们要将登录格式改为表单
           .loginPage("/login.html")//用来指定默认登录界面,一旦自定义登录页面以后必须只能登录url
           .loginProcessingUrl("/toLogin")//指定处理登录请求 url。前面说请求路径是/login,在这里可以修改。
           .successHandler(mySuccessHandler)//方法是定义一个认证成功后的处理逻辑。在认证成功后,我们可以重定向到其他页面或设置一些用户信息等
           .failureHandler(myFailureHandler)//自定义认证失败后的处理
           .permitAll()//公告资源(所用用户都可以使用)
        //*
           .and()
           .build();
}
/*
还有很多可以配置,都可以试一试
.usernameParameter()    自定义姓名name属性名字
.passwordParameter()    自定义密码name属性名字
.successForwardUrl()    认证成功后跳转页面到指定页面
.defaultSuccessUrl()    认证成功后跳转页面到指定页面,与上面的区别是:重定向,会记录被拦截前的页面认证成功后跳转到之前的页面,如果之前没有页面就跳转到指定页面
*/

3.认证成功处理和失败处理

在前面的学习中,我们知道UsernamePasswordAuthenticationFilter成功执行AuthenticationSuccessHandler,失败执行AuthenticationFailureHandler,所以我们分别实现他们俩个好了。

@Configuration
public class MySuccessHandler implements AuthenticationSuccessHandler {
    @Autowired
    private ObjectMapper objectMapper;
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        response.getWriter().println(objectMapper.writeValueAsString(HttpResult.ok("登录成功")));
    }
}
@Configuration
public class MyFailureHandler implements AuthenticationFailureHandler {
    @Autowired
    private ObjectMapper objectMapper;
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        PrintWriter writer = response.getWriter();
​
        String JsonResult = null;
        if(exception instanceof LockedException){
            JsonResult = objectMapper.writeValueAsString(HttpResult.fail(999,"账户被锁定!"));
        }
        else if(exception instanceof UsernameNotFoundException){
            JsonResult = objectMapper.writeValueAsString(HttpResult.fail(998,"用户不存在"));
        }
        else if(exception instanceof BadCredentialsException){
            JsonResult = objectMapper.writeValueAsString(HttpResult.fail(997,"密码错误"));
        }
        writer.write(JsonResult);
    }
}

自定义注销功能

1.在配置类中做配置

@Autowired
private MyLogoutSuccessHandler myLogoutSuccessHandler;
​
@Bean
public SecurityFilterChain chain(HttpSecurity http) throws Exception {
    return http
        .csrf().disable()
        .authorizeRequests()
        .anyRequest().authenticated()
        .and()
        .logout()
        .logoutRequestMatcher(new OrRequestMatcher(
              new AntPathRequestMatcher("/toLogout","GET"),
              new AntPathRequestMatcher("/exit","POST")
         ))//自定义注销登录,可以写多个请求
         .invalidateHttpSession(true)   //默认true 会话失效
         .clearAuthentication(true)     //默认true 清除认证标记
         .logoutSuccessHandler(myLogoutSuccessHandler)  //自定义注销处理
         .permitAll()
         .and()
         .build();
}
/*
logoutSuccessUrl("/test")   注销登录 成功之后跳转页面
logoutUrl("/logout")    指定注销登录url默认请求方式必须GET
*/

2.自定义注销处理

@Configuration
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    @Autowired
    private ObjectMapper objectMapper;
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        response.getWriter().println(objectMapper.writeValueAsString(HttpResult.ok("注销成功")));
    }
}

配置文件

@Configuration
@EnableWebSecurity
public class SecurityConfig{
    @Bean
    public SecurityFilterChain chain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/login").anonymous()
            .anyRequest().authenticated()
        return http.build();
    }
}
/*
anonymous() 匿名放行资源(匿名用户所使用的)
permitAll() 公告资源(所用用户都可以使用)
anyRequest().authenticated() 代表所用请求,必须认证之后才能访问
formLogin() 代表开启表单验证
注意:放行资源必须放在所在请求之前
*/
/*提供了一种抽象的方式来自定义 Spring Security 的配置,可以用于修改 WebSecurityConfigurerAdapter 中定义的默认安全策略以及添加自定义的安全策略。
放行/js和/css中的文件
*/
@Bean
public WebSecurityCustomizer customizer(){
    return web -> web.ignoring().antMatchers("/js/**","css/**");
}

权限功能

配置类

@EnableGlobalMethodSecurity(prePostEnabled = true)//在配置类加上这个注解

让后在需要权限的接口上添加权限即可

    @GetMapping("/")
    @PreAuthorize("hasAuthority('read')")
    public HttpResult user(){
        return HttpResult.ok("hello user","cgl");
    }
//这个接口就需要携带‘read’权限的用户才可以访问。

自定义hasAuthority

@Component("root")
public class MyExpressionRoot {
    public boolean hasAuthority(String authority){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        boolean contains = authorities.contains(new CustomAuthority(authority));
        return contains;
    }
}
//自定义的好处在与更加灵活,可以实现更多功能