文档章节

Spring Boot + Spring Security自定义用户认证

 心田已荒
发布于 07/05 21:15
字数 2244
阅读 157
收藏 0

「深度学习福利」大神带你进阶工程师,立即查看>>>

  • 引入依赖:
  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
		 <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
  • 自定义认证过程 自定义认证的过程需要实现Spring Security提供的UserDetailService接口 ,源码如下:
public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

loadUserByUsername方法返回一个UserDetail对象,该对象也是一个接口,包含一些用于描述用户信息的方法,源码如下:

public interface UserDetails extends Serializable {
	// 获取用户包含的权限,返回权限集合,权限是一个继承了GrantedAuthority的对象;
    Collection<? extends GrantedAuthority> getAuthorities();
	// 获取密码
    String getPassword();
  // 获取账号/用户名
    String getUsername();
	// 账户是否过期
    boolean isAccountNonExpired();
	//账户是否被锁定
    boolean isAccountNonLocked();
	//用户凭证是否过期
    boolean isCredentialsNonExpired();
	//用户是否可用
    boolean isEnabled();
}
  • 创建实现自定义认证接口的类:
@Configuration
public class UserDetailService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 模拟一个用户,实际项目中应为: 根据用户名查找数据库,如果没有记录则会返回null,有则返回UserDetails对象
        MyUser user = new MyUser();
        user.setUserName(username);
        user.setPassword(this.passwordEncoder.encode("123456"));
        // 输出加密后的密码
        System.out.println(user.getPassword());
		// 返回对象之后 会在内部进行认证(密码/盐/加密过密码等)
        return new User(username, user.getPassword(), user.isEnabled(),
                user.isAccountNonExpired(), user.isCredentialsNonExpired(),
                user.isAccountNonLocked(), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}
  • 创建用户类
@Data
public class MyUser implements Serializable {
    private static final long serialVersionUID = 3497935890426858541L;

    private String userName;

    private String password;

    private boolean accountNonExpired = true;

    private boolean accountNonLocked= true;

    private boolean credentialsNonExpired= true;

    private boolean enabled= true;

}
  • 配置类:
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    ...
}
注:PasswordEncoder是一个密码加密接口,而BCryptPasswordEncoder是Spring Security提供的一个实现方法,我们也可以自己实现PasswordEncoder。
    不过Spring Security实现的BCryptPasswordEncoder已经足够强大,它对相同的密码进行加密后可以生成不同的结果

启动项目:访问http://localhost:8080/login, 便可以使用任意用户名以及123456作为密码登录系统

BCryptPasswordEncoder对相同的密码生成的结果每次都是不一样的

  • 替换默认登录页 直接在src/main/resources/resources目录下定义一个login.html(不需要Controller跳转)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <link rel="stylesheet" href="css/login.css" type="text/css">
</head>
<body>
    <form class="login-page" action="/login" method="post">
        <div class="form">
            <h3>账户登录</h3>
            <input type="text" placeholder="用户名" name="username" required="required" />
            <input type="password" placeholder="密码" name="password" required="required" />
            <button type="submit">登录</button>
        </div>
    </form>
</body>
</html>

在MySecurityConfig中添加:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin() // 表单登录
            // http.httpBasic() // HTTP Basic
            .loginPage("/login.html") //指定了跳转到登录页面的请求URL
            .loginProcessingUrl("/login") //对应登录页面form表单的action="/login"
            .and()
            .authorizeRequests() // 授权配置
			//.antMatchers("/login.html").permitAll()表示跳转到登录页面的请求不被拦截,否则会进入无限循环
            .antMatchers("/login.html").permitAll()
            .anyRequest()  // 所有请求
            .authenticated()// 都需要认证
			.and().csrf().disable(); // 关闭csrf防御
}

访问http://localhost:8080/hello ,会看到页面已经被重定向到了http://localhost:8080/login.html 使用任意用户名+密码123456登录

在未登录的情况下,当用户访问html资源的时候,如果已经登陆则返回JSON数据,否则直接跳转到登录页,状态码为401。

要实现这个功能我们将loginPage的URL改为/authentication/require,并且在antMatchers方法中加入该URL,让其免拦截:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin() // 表单登录
            // http.httpBasic() // HTTP Basic
            .loginPage("/authentication/require") // 登录跳转 URL
            .loginProcessingUrl("/login") // 处理表单登录 URL
            .and()
            .authorizeRequests() // 授权配置
            .antMatchers("/authentication/require", "/login.html").permitAll() // 登录跳转 URL 无需认证
            .anyRequest()  // 所有请求
            .authenticated() // 都需要认证
            .and().csrf().disable();
}

创建控制器MySecurityController,处理这个请求:

@RestController
public class MySecurityController {
	//RequestCache requestCache是Spring Security提供的用于缓存请求的对象
    private RequestCache requestCache = new HttpSessionRequestCache();
	//DefaultRedirectStrategy是Spring Security提供的重定向策略 
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @GetMapping("/authentication/require")
    @ResponseStatus(HttpStatus.UNAUTHORIZED)   //HttpStatus.UNAUTHORIZED 未认证 状态码401
    public String requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
				//getRequest方法可以获取到本次请求的HTTP信息
        SavedRequest savedRequest = requestCache.getRequest(request, response);
        if (savedRequest != null) {
            String targetUrl = savedRequest.getRedirectUrl();
            if (StringUtils.endsWithIgnoreCase(targetUrl, ".html"))
							//sendRedirect为Spring Security提供的用于处理重定向的方法
                redirectStrategy.sendRedirect(request, response, "/login.html");
        }
        return "访问的资源需要身份认证!";
    }
}

上面代码获取了引发跳转的请求,根据请求是否以.html为结尾来对应不同的处理方法。如果是以.html结尾,那么重定向到登录页面,否则返回”访问的资源需要身份认证!”信息,并且HTTP状态码为401(HttpStatus.UNAUTHORIZED)。

这样当我们访问http://localhost:8080/hello 的时候页面便会跳转到http://localhost:8080/authentication/require,

当我们访问http://localhost:8080/hello.html 的时候,页面将会跳转到登录页面。

  • 处理成功和失败 Spring Security有一套默认的处理登录成功和失败的方法:当用户登录成功时,页面会跳转会引发登录的请求,比如在未登录的情况下访问http://localhost:8080/hello, 页面会跳转到登录页,登录成功后再跳转回来;登录失败时则是跳转到Spring Security默认的错误提示页面。下面 通过一些自定义配置来替换这套默认的处理机制。

自定义登录成功逻辑 要改变默认的处理成功逻辑很简单,只需要实现org.springframework.security.web.authentication.AuthenticationSuccessHandler接口的onAuthenticationSuccess方法即可:

@Component
public class MyAuthenticationSucessHandler implements AuthenticationSuccessHandler {
    @Autowired
    private ObjectMapper mapper;
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
		// 将认证信息转换成jsonString写入response
        response.getWriter().write(mapper.writeValueAsString(authentication));
    }
}

其中Authentication参数既包含了认证请求的一些信息,比如IP,请求的SessionId等,也包含了用户信息,即前面提到的User对象。通过上面这个配置,用户登录成功后页面将打印出Authentication对象的信息。

要使这个配置生效,我们还在MySecurityConfig的configure中配置它:

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyAuthenticationSucessHandler authenticationSucessHandler;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin() // 表单登录
                // http.httpBasic() // HTTP Basic
                .loginPage("/authentication/require") // 登录跳转 URL
                .loginProcessingUrl("/login") // 处理表单登录 URL
                .successHandler(authenticationSucessHandler) // 处理登录成功
                .and()
                .authorizeRequests() // 授权配置
                .antMatchers("/authentication/require", "/login.html").permitAll() // 登录跳转 URL 无需认证
                .anyRequest()  // 所有请求
                .authenticated() // 都需要认证
                .and().csrf().disable();
    }
}

我们将MyAuthenticationSucessHandler注入进来,并通过successHandler方法进行配置。

这时候重启项目登录后页面将会输出如下JSON信息:

像password,credentials这些敏感信息,Spring Security已经将其屏蔽。

除此之外,我们也可以在登录成功后做页面的跳转,修改MyAuthenticationSucessHandler:

@Component
public class MyAuthenticationSucessHandler implements AuthenticationSuccessHandler {
    private RequestCache requestCache = new HttpSessionRequestCache();
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException {
        SavedRequest savedRequest = requestCache.getRequest(request, response);
        redirectStrategy.sendRedirect(request, response, savedRequest.getRedirectUrl());
    }
}

通过上面配置,登录成功后页面将跳转回引发跳转的页面。如果想指定跳转的页面,比如跳转到/index,可以将savedRequest.getRedirectUrl()修改为/index:

@Component
public class MyAuthenticationSucessHandler implements AuthenticationSuccessHandler {
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException {
        redirectStrategy.sendRedirect(request, response, "/index");
    }
}

在IndexController中定义一个处理该请求的方法:

@RestController
public class IndexController {
    @GetMapping("index")
    public Object index(){
        return SecurityContextHolder.getContext().getAuthentication();
    }
}

登录成功后,便可以使用SecurityContextHolder.getContext().getAuthentication()获取到Authentication对象信息。除了通过这种方式获取Authentication对象信息外,也可以使用下面这种方式:

@RestController
public class IndexController {
    @GetMapping("index")
    public Object index(Authentication authentication) {
        return authentication;
    }
}

重启项目,登录成功后,页面将跳转到http://localhost:8080/index:

  • 自定义登录失败逻辑 和自定义登录成功处理逻辑类似,自定义登录失败处理逻辑需要实现org.springframework.security.web.authentication.AuthenticationFailureHandler的onAuthenticationFailure方法:
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException {
    }
}

onAuthenticationFailure方法的AuthenticationException参数是一个抽象类,Spring Security根据登录失败的原因封装了许多对应的实现类,

不同的失败原因对应不同的异常,比如用户名或密码错误对应的是BadCredentialsException,用户不存在对应的是UsernameNotFoundException,用户被锁定对应的是LockedException等。

假如我们需要在登录失败的时候返回失败信息,可以这样处理:

@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Autowired
    private ObjectMapper mapper;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException {
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(mapper.writeValueAsString(exception.getMessage()));
    }
}

状态码定义为500(HttpStatus.INTERNAL_SERVER_ERROR.value()),即系统内部异常。

同样的,我们需要在BrowserSecurityConfig的configure中配置它:

@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyAuthenticationSucessHandler authenticationSucessHandler;

    @Autowired
    private MyAuthenticationFailureHandler authenticationFailureHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin() // 表单登录
                // http.httpBasic() // HTTP Basic
                .loginPage("/authentication/require") // 登录跳转 URL
                .loginProcessingUrl("/login") // 处理表单登录 URL
                .successHandler(authenticationSucessHandler) // 处理登录成功
                .failureHandler(authenticationFailureHandler) // 处理登录失败
                .and()
                .authorizeRequests() // 授权配置
                .antMatchers("/authentication/require", "/login.html").permitAll() // 登录跳转 URL 无需认证
                .anyRequest()  // 所有请求
                .authenticated() // 都需要认证
                .and().csrf().disable();
    }
}

重启项目之后,使用错误的密码登录 图示如下:

本博文代码均经过测试,可以正常运行!

源码地址: https://github.com/ttdys/springboot/tree/master/springboot_security/02_custom_authentication

粉丝 0
博文 42
码字总数 26104
作品 0
许昌
私信 提问
加载中
请先登录后再评论。
访问安全控制解决方案

本文是《轻量级 Java Web 框架架构设计》的系列博文。 今天想和大家简单的分享一下,在 Smart 中是如何做到访问安全控制的。也就是说,当没有登录或 Session 过期时所做的操作,会自动退回到...

黄勇
2013/11/03
3.6K
8
我的架构演化笔记 功能1: 基本的用户注册

“咚咚”,一阵急促的敲门声, 我从睡梦中惊醒,我靠,这才几点,谁这么早, 开门一看,原来我的小表弟放暑假了,来南京玩,顺便说跟我后面学习一个网站是怎么做出来的。 于是有了下面的一段...

强子哥哥
2014/05/31
976
3
自定义表单系统--FormDesign

是基于 FLEX 开发的一款B/S的自定义表单系统 整个过程如下: 1、通过FormDesign自定义表单画出相应的表单; 2、将生成好的XML放到程序中,并通过后台自动编译成JSP; 注:FormDesing只是自定...

polliwog
2013/03/29
1.6W
1
强制认证门户--Opengate

Opengate是在公共网络环境对用户进行身份验证和使用日志记录的一个系统,强制认证门户(captive portal)。 特点: 简单的用户界面: 使用客户机浏览器的图形用户界面交互。 广泛的适用性: ...

匿名
2012/11/04
3.9K
0
高效 Java Web 开发框架--JessMA

JessMA 是功能完备的高性能 Full-Stack Web 应用开发框架,内置可扩展的 MVC Web 基础架构和 DAO 数据库访问组件(内部已提供了 Hibernate、MyBatis 与 JDBC DAO 组件),集成了 Action 拦截...

伤神小怪兽
2012/11/13
9.3K
3

没有更多内容

加载失败,请刷新页面

加载更多

自制超声波驱狗器(第三版)

文档标识符:Ultrasonic_Dog_Repellent_II_T-D-P7 作者:DLHC 最后修改日期:2020.8.13 本文链接: https://www.cnblogs.com/DLHC-TECH/p/Ultrasonic_Dog_Repellent_II_T-D-P7.html “威力”......

osc_t4kk3au7
6分钟前
0
0
测试框架mocha入门

单元测试 今天带你了解下测试框架mocha,这是一个js的测试框架,而且适用于node和浏览器环境。通过它,我们可以为我们模块、组件级别以上的代码编写单元测试用例,保证代码输出质量。 一、安...

字节逆旅
昨天
0
0
ElasticSearch 7.8.1集群搭建

通往集群的大门 集群由什么用? 高可用   高可用(High Availability)是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。如果系统每运行100个时间...

osc_hwc3munb
8分钟前
0
0
如何面对人生危机?

点击蓝字关注,回复“职场进阶”获取职场进阶精品资料一份 一名读者提问:洋哥,我7年前从大厂出来,创业多年。连续失败,没买车也没房,女朋友也和我分手了,父母也对我失望至极。最近我开始...

张善友
今天
0
0
手写AOP实现过程

一.手写Aop前基础知识 1.aop是什么? 面向切面编程(AOP):是一种编程范式,提供从另一个角度来考虑程序结构从而完善面向对象编程(OOP)。 在进行OOP开发时,都是基于对组件(比如类)进行开发...

osc_qyg23ccq
8分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部