SpringBoot整合Spring Security

从事WEB开发依赖,一半用的安全认证框架,最多就是有两种:Apache Shrio和Spring Security,而Spring Security作为Spring全家桶中的一员,相对于Spring集成起来更具有优势和更好的支持!

Spring Security简介及SpringBoot整合Spring Security
Spring Security简介及SpringBoot整合Spring Security

Spring Security是一个强大且可定制的身份验证和访问控制框架,完全基于Spring的应用程序标准,它能够为基于Spring的企业应用系统提供安全访问控制解决方案的安全框架,它提供了一组可以在Spring应用上下文中配置的bean,充分利用了Spring IoC和AOP功能。用过apache shrio安全框架的码友们都知道,安全认证框架主要包含两个操作:认证(Authentication)和授权(Authorization)。Spring Security基于上述的两个操作,也是提供了多个模块:

1、核心模块(spring-security-core.jar):包含核心的验证和访问控制类和接口,远程支持和基本的配置API。任何使用Spring Security的应用程序都需要这个模块。支持独立应用程序、远程客户端、服务层方法安全和JDBC用户配置。

2、远程调用(
spring-security-remoting.jar):提供与Spring Remoting的集成,通常我们不需要这个模块,除非你要使用Spring Remoting编写远程客户端。

3、Web网页(spring-security-web.jar):包含网站安全相关的基础代码,包括Spring security网页验证服务和基于URL的访问控制。

4、配置(
spring-security-config.jar):包含安全命令空间的解析代码。如果你使用Spring Security XML命令空间进行配置你需要包含这个模块。

5、LDAP(spring-security-ldap.jar):LDAP验证和配置代码,这个模块可用于LDAP验证和管理LDAP用户实体。

6、ACL访问控制(spring-security-acl.jar):ACL专门的领域对象的实现。用来在你的应用程序中对特定的领域对象实例应用安全性。

7、CAS(spring-security-cas.jar):Spring Security的CAS客户端集成。如果你想用CAS的SSO服务器使用Spring Security网页验证需要该模块。

8、OpenID(
spring-security-openid.jar):OpenID 网页验证支持。使用外部的OpenID服务器验证用户。

9、Test(spring-security-test.jar):支持Spring security的测试。

Spring Security简介及SpringBoot整合Spring Security

SpringBoot中配置Spring security中非常简单,在pom.xml文件中加入Spring security的依赖,由于要使用静态模板的支持,所以把thymeleaf的依赖也给引入进来:

Spring Security简介及SpringBoot整合Spring Security

需要创建一个自定义类继承
WebSecurityConfigurerAdapter:

Spring Security简介及SpringBoot整合Spring Security
Spring Security简介及SpringBoot整合Spring Security


WebSecurityConfigurerAdapter是Spring security为Web应用提供的一个适配器,实现了WebSecurityConfigurer接口,提供了两个方法用于重写,从而实现开发者需求的安全配置。

方法configure(HttpSecurity http)可以通过http.authorizeRequests()定义哪些URL需要保护、哪些URL不需要,通过formLogin()方法定义当前用户登陆的时候,跳转到的登陆页面。

方法configure(
AuthenticationManagerBuilder auth)用于创建用户和用户的角色。

该自定义类代码如下:

package com.datang.security;

import org.springframework.beans.factory.annotation.Autowired;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class MySecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        System.out.println("MySecurityConfigurer HttpSecurity 调用...");
        http.authorizeRequests()
                .antMatchers("/login").permitAll()  //都可以访问路径/login
                .antMatchers("/", "/home").hasRole("ROCKET") //角色ROCKET可访问
                .antMatchers("/admin/**").hasAnyRole("LAKER", "HEAT")  //角色LAKER、HEAT可访问
                .anyRequest().authenticated()  //其他URL需要认证
                .and()
                .formLogin().loginPage("/login") //登陆请求
                .successHandler(new MyAuthenticationSuccessHandler())  //登陆成功后的行为
                .usernameParameter("username").passwordParameter("password")  //登陆时接收的参数
                .and()
                .logout().permitAll()  //登出,都可以访问
                .and()
                .exceptionHandling().accessDeniedPage("/accessDenied"); //异常处理
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        System.out.println("MySecurityConfigurer AuthenticationManagerBuilder 调用...");
        auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder())
                .withUser("t-mac").password("rocket1").roles("ROCKET");  //创建麦迪
        auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder())
                .withUser("james").password("laker23").roles("LAKER", "HEAT");  //创建詹姆斯
    }
}

上面代码中,当登陆成功后会有一个处理,就是登陆成功后怎么跳转,也就是我们自己定义的handler:new
MyAuthenticationSuccessHandler(),该类的代码如下:

package com.datang.security;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Component
public class MyAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    //负责所有重定向事务
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        String targetUrl = directTargetUrl(authentication);
        redirectStrategy.sendRedirect(request, response, targetUrl);
    }

    protected String directTargetUrl(Authentication authentication) {
        String url = "";
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        List roles = new ArrayList<>();
        for (GrantedAuthority a: authorities) {
            roles.add(a.getAuthority());
        }
        if (isAdmin(roles)) {
            url = "/admin";
        } else if (isUser(roles)) {
            url = "/home";
        } else {
            url = "/accessDenied";
        }
        System.out.println("url = "   url);
        return url;
    }

    private boolean isUser(List roles) {
        if (roles.contains("ROLE_ROCKET")) {
            return true;
        } else {
            return false;
        }
    }

    private boolean isAdmin(List roles) {
        if (roles.contains("ROLE_LAKER")) {
            return true;
        } else {
            return false;
        }
    }

}

该类继承了
SimpleUrlAuthenticationSuccessHandler,提供了handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication)方法用于处理登陆成功后的URL重定向,其中directTargetUrl()方法是要获取当前登陆者的角色,然后根据角色重定向到指定的URL。

在我们定义的MySecurityConfigurer类中,configure(
AuthenticationManagerBuilder auth)方法也出现了一个我们自定义的类:new MyPasswordEncoder(),该类的代码是:


它实现了PasswordEncoder接口,并实现了接口的两个方法:encode()和matches(),这是一个密码编辑器。

好了,现在基础工作以及准备的差不多了,现在就写个controller类来测试一下,首先写一个基础的BaseController,该类中实现了获取用户名getUsername()和获取角色getAuthority()的方法,代码如下:

package com.datang.controller;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;

import java.util.ArrayList;
import java.util.List;

public class BaseController {

    protected String getUsername() {
        String name = SecurityContextHolder.getContext().getAuthentication().getName();
        return name;
    }

    protected String getAuthority() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        List roles = new ArrayList<>();
        for (GrantedAuthority a:authentication.getAuthorities()) {
            roles.add(a.getAuthority());
        }
        return roles.toString();
    }
}

然后写一个测试类MySecurityController,继承BaseController,其中代码如下:

package com.datang.controller;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
public class MySecurityController extends BaseController{

    @RequestMapping("/")
    @ResponseBody
    public String index() {
        return "首页";
    }

    @RequestMapping("/home")
    @ResponseBody
    public String home() {
        String username = getUsername();
        System.out.println("username = "   username);
        return "主页 - 用户名:"   username;
    }

    @RequestMapping("/login")
    public String login() {
        return "login";
    }

    @RequestMapping("/admin")
    @ResponseBody
    public String admin() {
        String username = getUsername();
        String role = getAuthority();
        return "管理页 - 用户名:"   username   " - 角色:"   role;
    }

    @RequestMapping("/nba")
    @ResponseBody
    public String nba() {
        String username = getUsername();
        String role = getAuthority();
        return "NBA - 用户名:"   username   " - 角色:"   role;
    }

    @RequestMapping("/accessDenied")
    @ResponseBody
    public String accessDenied() {
        return "无权限";
    }

    @RequestMapping("/logout")
    @ResponseBody
    public String logout(HttpServletRequest request, HttpServletResponse response) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null) {
            new SecurityContextLogoutHandler().logout(request, response, authentication);
        }
        return "用户登出";
    }
}

由于是简单的测试,所以就凑合写一个简单的登陆页面login.html,其他页面就用返回字符串来代替!

login.html代码如下:

html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <title>登录title>
head>
<body class="text-center">
<form class="form-signin" th:action="@{/login}" method="post">
    <h1 class="h3 mb-3 font-weight-normal">请登录h1>
    <label for="username" class="sr-only">用户名label>
    <input type="text" id="username" name="username" class="form-control" placeholder="username" required="" autofocus="">
    <label for="password" class="sr-only">密码label>
    <input type="password" id="password" name="password" class="form-control" placeholder="password" required="">
    <button type="submit">登录button>
form>
body>
html>

然后整体代码结构如下,红色边框外的类和包自行忽略呀:

Spring Security简介及SpringBoot整合Spring Security

启动这个demo:

Spring Security简介及SpringBoot整合Spring Security

看了咱们定义的URL访问规则和用户创建生效了,下面可以访问了,输入URL:http://localhost:9092/nba或者其他的诸如http://localhost:9092/aaa,会被重定向到
http://localhost:9092/login,因为没有登陆:

Spring Security简介及SpringBoot整合Spring Security

输入用户james登陆,会被重定向到/admin,输入t-mac会被重定向到/home:

Spring Security简介及SpringBoot整合Spring Security
Spring Security简介及SpringBoot整合Spring Security

此时再访问http://localhost:9092/nba就可以了:

Spring Security简介及SpringBoot整合Spring Security

其他登出/logout也是正常登出的,自个儿动手试一下吧!

内容出处:,

声明:本网站所收集的部分公开资料来源于互联网,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。如果您发现网站上有侵犯您的知识产权的作品,请与我们取得联系,我们会及时修改或删除。文章链接:http://www.yixao.net/tech/21741.html

发表评论

登录后才能评论

评论列表(1条)

  • 爱上你了
    爱上你了 2021-07-17 06:09

    Java开发太落后了,配置太多,完全是个手动挡,档位还几十个,也难怪springboot流行起来. Ioc/Aop确实是精髓,但是啥啥都要个配置那就是糟粕了