30-SpringBoot——核心-企业级开发-Spring Security

Spring Boot & Spring Cloud 同时被 2 个专栏收录
43 篇文章 0 订阅
32 篇文章 16 订阅

Spring Boot核心-企业级开发-Spring Security


【博文目录>>>】


【项目源码>>>】


Spring Security 快速入门


什么是Spring Security


Sprirg security 是专门针对基于Spring 的项目的安全框架,充分利用了依赖注入和AOP来实现安全的功能。

在早期的Spring Security 版本,使用Spring Security 需要使用大量的XML 配置,而本节将全部基于Java 配置来实现Spring Security 的功能。

安全框架有两个重要的概念,即认证( Authentication )和授权( Authorization )。认证即确认用户可以访问当前系统:授权即确定用户在当前系统下所拥有的功能权限,本节将围绕认证和授权展开。
Spring Security 的配置

(1)DelegatingFilterProxy

Spring Security 为我们提供了一个多个过滤器来实现所有安全的功能,我们只需注册一个特殊的DelegatingFilterProxy 过滤器到WebApplicationlnitializer 即可。而在实际使用中,我们只需让自己的Initializer 类继承AbstractSecurityWebApplicationlnitializer 抽象类即可。AbstractSecurityWebApplicationinitializer 实现了WebApplicationinitializer 接口,并通过onStartup 方法调用。它为我们注册了DelegatingFilterProxy 。insertSpringSecurityFilterChain 源码如下:

private void insertSpringSecurityFilterChain(ServletContext servletContext) {
   String filterName = DEFAULT_FILTER_NAME;
   DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(
         filterName);
   String contextAttribute = getWebApplicationContextAttribute();
   if (contextAttribute != null) {
      springSecurityFilterChain.setContextAttribute(contextAttribute);
   }
   registerFilter(servletContext, true, filterName, springSecurityFilterChain);
}
所以我们只需用以下代码即可开启Spring Security 的过滤器支持:
public class AppInitializerSample extends AbstractSecurityWebApplicationInitializer {
}

(2)配置

Spring Security 的配置和Spring MVC 的配置类似,只需在一个配置类上注解@EnableWebSecurity ,并让这个类继承WebSecurityConfigAdapter即可,我们可与重写configure 方法来配置相关的安全配置。

@Configuration
public class WebSecurityConfigSample extends WebSecurityConfigurerAdapter {//1

@Bean
UserDetailsService customUserService() { //2
    return new CustomUserService();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    super.configure(auth);
}
    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
    }
}

用户认证


认证需要我们有一套用户数据的来源, 而授权则是对于某个用户有相应的角色权限。在Spring Security 但我们通过重写方法来实现定制。

protected void configure(AuthenticationManagerBuilder auth) throws Exception {}

( 1 )内存中的用户

使用AuthenticationManagerBuilder 的inMemoryAuthentication 方法即可添加在内存中的用户,并可给用户指定角色权限。

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {

    auth.inMemoryAuthentication()
            .withUser("wjc").password("123456").roles("ROLE_ADMIN")
            .and()
            .withUser("wisely").password("123456").roles("ROLE_USER");
}

(2) JDBC 中的用户

JDBC 中的用户直接指定dataSource 即可。

@Autowired
private DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.jdbcAuthentication().dataSource(dataSource);
}

不过这看上去很奇怪,其实这里的Spring Security 是默认了你的数据库结构的。通过jdbcAuthentication 的源码,我们可以看出在JdbcDaolmpl 中定义了默认的用户及角色权限获取的SQL 语句:

public static final String DEF_USERS_BY_USERNAME_QUERY = "select username,password,enabled "
      + "from users " + "where username = ?";
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "select username,authority "
      + "from authorities " + "where username = ?";
public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY = "select g.id, g.group_name, ga.authority "
      + "from groups g, group_members gm, group_authorities ga "
      + "where gm.username = ? " + "and g.id = ga.group_id "
      + "and g.id = gm.group_id";

我们可以自定义我们的查询用户和权限的SQL 语句

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.jdbcAuthentication().dataSource(dataSource)
            .usersByUsernameQuery("select username,password,true "
                    + "from t_users " + "where username = ?")
    .authoritiesByUsernameQuery("select username,authority "
            + "from t_authorities " + "where username = ?");
}

(3)通用的用户

上面的两种用户和权限的获取方式只限于内存或者JDBC ,我们的数据访问方式可以是多种各样的,可以是非关系型数据库,也可以是我们常用的JPA 等。这时我们需要自定义实现UserDetailsService 接口。上面的内存中用户及JDBC 用户就是User Details Service 的实现,定义如下:

@Configuration
public class CustomUserServiceSample implements UserDetailsService {
    @Autowired
    SysUserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = userRepository.findByUsername(username);
        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        return new User(user.getUsername(), user.getPassword(), authorities);
    }
}

说明: SysUser 是我们系统的用户领域对象类, User 来自于org.springframework.security. core. userdetails.User。除此之外,我们还需要注册这个CustomU serService ,代码如下:

@Bean
UserDetailsService customUserService() {
    return new CustomUserService();
}
    @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(customUserService());
}

(4)请求授权

Spring Security 是通过重写
protected void configure(HttpSecurity http) throws Exception{}
方法来实现请求拦截的。Spring Security 使用以下匹配器来匹配请求路径:

antMatchers :使用Ant 风格的路径匹配。
regexMatchers :使用正则表达式匹配路径。

anyRequest:匹配所有请求路径。

在匹配了请求路径后,需要针对当前用户的信息对请求路径进行安全处理, Spring Security提供了的安全处理方法,如下图所示。

这里写图片描述

代码示例

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/admin/**").hasRole("ROLE_ADMIN")
            .antMatchers("/user/**").hasAnyRole("ROLE_ADMIN", "ROLE_USER")
            .anyRequest().authenticated();
}

说明

(1) 通过authorizeRequests 方法来开始请求权限自己宜。
(2) 请求匹配/admin**,只有拥有ROLE ADMIN 角色的用户可以访问。
(3) 请求匹配/user/**,拥有ROLE_ADMIN 或ROLE_USER 角色的用户都可访问。
(4) 其余所有的请求都需要认证后(登录后)才可访问。

也可以重写

protected void configure(HttpSecurity http) throws Exception{}

方法来定制我们的登录行为。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
            .loginPage("/login")
            .defaultSuccessUrl("/index")
            .failureUrl("/login?error")
            .permitAll()
            .and()
            .rememberMe()
            .tokenValiditySeconds(1000)
            .key("key")
            .and()
            .logout()
            .logoutUrl("/custom-logout")
            .logoutSuccessUrl("/logout-success")
            .permitAll();
}
(1) 通过formLogin 方法定制登录操作。
(2) 使用loginPage 方法定制登录页面的访问地址。
(3) defaultSuccessUrl 指定登录成功后转向的页面。
(4) failure Uri 指定登录失败后转向的页面。
(5) remember Me 开启cookie 存储用户信息。
(6) token ValiditySeconds 指定cookie 有效期为1209600 秒,即2 个星期。
(7) key 指定cookie 中的私钥。
(8) 使用logout 方法定制注销行为。
(9) logoutUrl 指定注销的URL 路径。
(10)    logoutSuccessUrl 指定注销成功后转向的页面。

Spring Boot 的支持


Spring Boot 针对Spring Security 的自动配置在org.springframework.boot.autoconfigure.security 包中。主要通过SecurityAutoConfiguration 和SecurityProperties 来完成配置。Security AutoConfiguration 导入了SpringBootWebSecurityConfiguration 中的配置。在SpringBootWebSecurityConfiguratioh 配置中,我们获得如下的自动配置:

(1) 自动配置了一个内存中的用户,账号为user,密码在程序启动时出现。
(2) 忽略/css/**、/js/**、/images/**和/**/favicon.ico 等静态文件的拦截。
(3) 自动配置的securityFilterChainRegistration 的Bean。

Security Properties 使用以“ security ”为前缀的属性配置Spring Security 相关的配置,包含:

# ----------------------------------------
# SECURITY PROPERTIES
# ----------------------------------------
# SECURITY (SecurityProperties)
security.basic.authorize-mode=role # Security authorize mode to apply.
security.basic.enabled=true # Enable basic authentication.
security.basic.path=/** # Comma-separated list of paths to secure.
security.basic.realm=Spring # HTTP basic realm name.
security.enable-csrf=false # Enable Cross Site Request Forgery support.
security.filter-order=0 # Security filter chain order.
security.filter-dispatcher-types=ASYNC, FORWARD, INCLUDE, REQUEST # Security filter chain dispatcher types.
security.headers.cache=true # Enable cache control HTTP headers.
security.headers.content-security-policy= # Value for content security policy header.
security.headers.content-security-policy-mode=default # Content security policy mode.
security.headers.content-type=true # Enable "X-Content-Type-Options" header.
security.headers.frame=true # Enable "X-Frame-Options" header.
security.headers.hsts=all # HTTP Strict Transport Security (HSTS) mode (none, domain, all).
security.headers.xss=true # Enable cross site scripting (XSS) protection.
security.ignored= # Comma-separated list of paths to exclude from the default secured paths.
security.require-ssl=false # Enable secure channel for all requests.
security.sessions=stateless # Session creation policy (always, never, if_required, stateless).
security.user.name=user # Default user name.
security.user.password= # Password for the default user name. A random password is logged on startup by default.
security.user.role=USER # Granted roles for the default user name.

Spring Boot 为我们做了如此多的配置,当我们需要自己扩展的配置时,只需配置类继承WebSecurityConfigurerAdapter 类即可。

代码实现

application.properties

spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc\:mysql\://localhost\:3306/springboot
spring.datasource.username=root
spring.datasource.password=123456
logging.level.org.springframework.security=INFO
spring.thymeleaf.cache=false
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

data.sql

spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc\:mysql\://localhost\:3306/springboot
spring.datasource.username=root
spring.datasource.password=123456
logging.level.org.springframework.security=INFO
spring.thymeleaf.cache=false
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

home.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"><!-- 1 -->
<head>
    <meta content="text/html;charset=UTF-8"/>
    <title sec:authentication="name"></title> <!-- 2 -->
    <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
    <style type="text/css">
        body {
            padding-top: 50px;
        }

        .starter-template {
            padding: 40px 15px;
            text-align: center;
        }
    </style>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container">
        <div class="navbar-header">
            <a class="navbar-brand" href="#">Spring Security演示</a>
        </div>
        <div id="navbar" class="collapse navbar-collapse">
            <ul class="nav navbar-nav">
                <li><a th:href="@{/}"> 首页 </a></li>

            </ul>
        </div><!--/.nav-collapse -->
    </div>
</nav>


<div class="container">

    <div class="starter-template">
        <h1 th:text="${msg.title}"></h1>

        <p class="bg-primary" th:text="${msg.content}"></p>

        <div sec:authorize="hasRole('ROLE_ADMIN')"> <!-- 3 -->
            <p class="bg-info" th:text="${msg.etraInfo}"></p>
        </div>

        <div sec:authorize="hasRole('ROLE_USER')"> <!-- 4-->
            <p class="bg-info">无更多信息显示</p>
        </div>

        <form th:action="@{/logout}" method="post">
            <input type="submit" class="btn btn-primary" value="注销"/><!-- 5 -->
        </form>
    </div>

</div>


</body>
</html>

login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta content="text/html;charset=UTF-8"/>
    <title>登录页面</title>
    <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
    <style type="text/css">
        body {
            padding-top: 50px;
        }

        .starter-template {
            padding: 40px 15px;
            text-align: center;
        }
    </style>
</head>
<body>

<nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container">
        <div class="navbar-header">
            <a class="navbar-brand" href="#">Spring Security演示</a>
        </div>
        <div id="navbar" class="collapse navbar-collapse">
            <ul class="nav navbar-nav">
                <li><a th:href="@{/}"> 首页 </a></li>

            </ul>
        </div><!--/.nav-collapse -->
    </div>
</nav>
<div class="container">

    <div class="starter-template">
        <p th:if="${param.logout}" class="bg-warning">已成功注销</p><!-- 1 -->
        <p th:if="${param.error}" class="bg-danger">有错误,请重试</p> <!-- 2 -->
        <h2>使用账号密码登录</h2>
        <form name="form" th:action="@{/login}" action="/login" method="POST"> <!-- 3 -->
            <div class="form-group">
                <label for="username">账号</label>
                <input type="text" class="form-control" name="username" value="" placeholder="账号"/>
            </div>
            <div class="form-group">
                <label for="password">密码</label>
                <input type="password" class="form-control" name="password" placeholder="密码"/>
            </div>
            <input type="submit" id="login" value="Login" class="btn btn-primary"/>
        </form>
    </div>

</div>

</body>
</html>
package com.example.spring.boot.security;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Author: 王俊超
 * Date: 2017-07-19 08:01
 * All Rights Reserved !!!
 */
@SpringBootApplication
public class SampleApplication {
    public static void main(String[] args) {
        SpringApplication.run(SampleApplication.class, args);
    }
}
package com.example.spring.boot.security.service;

import com.example.spring.boot.security.dao.SysUserRepository;
import com.example.spring.boot.security.domain.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class CustomUserService implements UserDetailsService { //1
    @Autowired
    private SysUserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) { //2

        SysUser user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }

        return user; //3
    }

}
package com.example.spring.boot.security.domain;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Entity
public class SysUser implements UserDetails { //1

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue
    private Long id;
    private String username;
    private String password;
    @ManyToMany(cascade = {CascadeType.REFRESH}, fetch = FetchType.EAGER)
    private List<SysRole> roles;


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() { //2
        List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
        List<SysRole> roles = this.getRoles();
        for (SysRole role : roles) {
            auths.add(new SimpleGrantedAuthority(role.getName()));
        }
        return auths;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public List<SysRole> getRoles() {
        return roles;
    }

    public void setRoles(List<SysRole> roles) {
        this.roles = roles;
    }


}
package com.example.spring.boot.security.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class SysRole {
    @Id
    @GeneratedValue
    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }


}
package com.example.spring.boot.security.domain;

public class Msg {
    private String title;
    private String content;
    private String etraInfo;

    public Msg(String title, String content, String etraInfo) {
        super();
        this.title = title;
        this.content = content;
        this.etraInfo = etraInfo;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getEtraInfo() {
        return etraInfo;
    }

    public void setEtraInfo(String etraInfo) {
        this.etraInfo = etraInfo;
    }

}
package com.example.spring.boot.security.dao;

import com.example.spring.boot.security.domain.SysUser;
import org.springframework.data.jpa.repository.JpaRepository;

public interface SysUserRepository extends JpaRepository<SysUser, Long> {

    SysUser findByUsername(String username);

}
package com.example.spring.boot.security.config;

import com.example.spring.boot.security.service.CustomUserService;
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.core.userdetails.UserDetailsService;

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {//1

    @Bean
    UserDetailsService customUserService() { //2
        return new CustomUserService();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserService()); //3

    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated() //4
                .and()
                .formLogin()
                .loginPage("/login")
                .failureUrl("/login?error")
                .permitAll() //5
                .and()
                .logout().permitAll(); //6
    }
}
package com.example.spring.boot.security.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
    }
}
  • 2
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

打赏
文章很值,打赏犒劳作者一下
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页

打赏

Wang-Junchao

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值