栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring Boot:整合Spring Security

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring Boot:整合Spring Security


学习目标:

Spring Security 应用级别的安全主要包含两个主要部分,即登录认证(Authentication)和访问授权(Authorization),首先用户登录的时候传入登录信息,登录验证器完成登录认证并将登录认证好的信息存储到请求上下文,然后再进行其他操作,如在进行接口访问、方法调用时,权限认证器从上下文中获取登录认证信息,然后根据认证信息获取权限信息,通过权限信息和特定的授权策略决定是否授权。

pring security 的核心功能主要包括:

认证 (你是谁)

授权 (你能干什么)

攻击防护 (防止伪造身份)

其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式



学习内容:

接下来,我们就通过一个具体的案例,来讲解如何进行Spring Security的整合,然后借助Spring Security实现登录认证和访问控制。

生成项目模板

为方便我们初始化项目,Spring Boot给我们提供一个项目模板生成网站。

1.  打开浏览器,访问:https://start.spring.io/

2.  根据页面提示,选择构建工具,开发语言,项目信息等。

3.  点击 Generate the project,生成项目模板,生成之后会将压缩包下载到本地。

4.  使用IDE导入项目,我这里使用Eclipse,通过导入Maven项目的方式导入。

添加相关依赖

清理掉不需要的测试类及测试依赖,添加 Maven 相关依赖,这里需要添加上web、swagger、spring security、jwt和fastjson的依赖,Swagge和fastjson的添加是为了方便接口测试。

pom.xml



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.5.RELEASE
         
    
    com.louis.springboot
    demo
    0.0.1-SNAPSHOT
    demo
    Demo project for Spring Boot

    
        1.8
    

    
        
        
                org.springframework.boot
                spring-boot-starter-web
         
        
        
            io.springfox
            springfox-swagger2
            2.9.2
        
        
            io.springfox
            springfox-swagger-ui
            2.9.2
        
        
        
            org.springframework.boot
            spring-boot-starter-security
        
        
        
            io.jsonwebtoken
            jjwt
            0.9.1
        
        
        
            com.alibaba
            fastjson
            1.2.58
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
        
        
            
                src/main/java
                
                    **/sqlmap*.*  
                     
                    true  
             
        
    

添加相关配置

1.添加swagger 配置

添加一个swagger 配置类,在工程下新建 config 包并添加一个 SwaggerConfig 配置类,除了常规配置外,加了一个令牌属性,可以在接口调用的时候传递令牌。令牌使用JWT token

SwaggerConfig.java

package com.rkhd.ienterprise.apps.bi.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.documentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

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


@Configuration
@EnableSwagger2
public class Swagger2Config {

    
    @Bean
    public Docket createRestApi(){
        // 添加请求参数,我们这里把token作为请求头部参数传入后端
        ParameterBuilder parameterBuilder = new ParameterBuilder();
        List parameters = new ArrayList();
        parameterBuilder.name("Authorization").description("令牌").modelRef(new ModelRef("string")).parameterType("header")
                .required(false).build();
        parameters.add(parameterBuilder.build());
        return new Docket(documentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any()).build().globalOperationParameters(parameters);
    }
    
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                //页面标题
                .title("BI数据智能API接口文档")
                //创建人
                .contact(new Contact("雷鹏程", "https://wiki.ingageapp.com/pages/viewpage.action?pageId=64266431","leipc@xiaoshouyi.com"))
                //版本号
                .version("1.0")
                //描述
                .description("BI系统API描述")
                .build();
    }

}

 swagger界面如下:

 2.添加跨域 配置

package com.rkhd.ienterprise.apps.bi.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 允许跨域访问的路径
        registry.addMapping("
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 使用自定义登录身份认证组件
        auth.authenticationProvider(new JwtAuthenticationProvider(userDetailsService));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 禁用 csrf, 由于使用的是JWT,我们这里不需要csrf
        http.cors().and().csrf().disable()
                .authorizeRequests()
                // 跨域预检请求
                .antMatchers(HttpMethod.OPTIONS, "**").permitAll()
                .antMatchers("/webjars
@Configuration
public class CaptchaConfig {

	@Bean
	public DefaultKaptcha defaultKaptcha(){
		//验证码生成器
		DefaultKaptcha defaultKaptcha=new DefaultKaptcha();
		//配置
		Properties properties = new Properties();
		//是否有边框
		properties.setProperty("kaptcha.border", "yes");
		//设置边框颜色
		properties.setProperty("kaptcha.border.color", "105,179,90");
		//边框粗细度,默认为1
		 properties.setProperty("kaptcha.border.thickness","1");
		//验证码
		properties.setProperty("kaptcha.session.key","code");
		//验证码文本字符颜色 默认为黑色
		properties.setProperty("kaptcha.textproducer.font.color", "blue");
		//设置字体样式
		properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
		//字体大小,默认40
		properties.setProperty("kaptcha.textproducer.font.size", "30");
		//字符长度,默认为5
		properties.setProperty("kaptcha.textproducer.char.length", "4");
		//字符间距 默认为2
		properties.setProperty("kaptcha.textproducer.char.space", "4");
		//验证码图片宽度 默认为200
		properties.setProperty("kaptcha.image.width", "100");
		//验证码图片高度 默认为40
		properties.setProperty("kaptcha.image.height", "40");
		Config config = new Config(properties);
		defaultKaptcha.setConfig(config);
		return defaultKaptcha;
	}

}
package com.rkhd.ienterprise.apps.bi.controller;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;


@RestController
public class CaptchaController {

	@Autowired
	private DefaultKaptcha defaultKaptcha;

	@ApiOperation(value = "验证码")
	@GetMapping(value = "/captcha",produces = "image/jpeg")
	public void captcha(HttpServletRequest request, HttpServletResponse response){
		// 定义response输出类型为image/jpeg类型
		response.setDateHeader("Expires", 0);
		// Set standard HTTP/1.1 no-cache headers.
		response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
		// Set IE extended HTTP/1.1 no-cache headers (use addHeader).
		response.addHeader("Cache-Control", "post-check=0, pre-check=0");
		// Set standard HTTP/1.0 no-cache header.
		response.setHeader("Pragma", "no-cache");
		// return a jpeg
		response.setContentType("image/jpeg");
		//-------------------生成验证码 begin --------------------------
		//获取验证码文本内容
		String text = defaultKaptcha.createText();
		System.out.println("验证码内容:"+text);
		//将验证码文本内容放入session
		request.getSession().setAttribute("captcha",text);
		//根据文本验证码内容创建图形验证码
		BufferedImage image = defaultKaptcha.createImage(text);
		ServletOutputStream outputStream = null;
		try {
			outputStream = response.getOutputStream();
			//输出流输出图片,格式为jpg
			ImageIO.write(image,"jpg",outputStream);
			outputStream.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
			if (null!=outputStream){
				try {
					outputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		//-------------------生成验证码 end --------------------------

	}

}
登录身份认证组件

上面说到登录认证是通过调用 AuthenticationManager 的 authenticate(token) 方法实现的,而 AuthenticationManager 又是通过调用 AuthenticationProvider 的 authenticate(Authentication authentication) 来完成认证的,所以通过定制 AuthenticationProvider 也可以完成各种自定义的需求,我们这里只是简单的继承 DaoAuthenticationProvider 展示如何自定义,具体的大家可以根据各自的需求按需定制。

package com.rkhd.ienterprise.apps.bi.security;

import com.rkhd.ienterprise.security.MD5Util;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;


public class JwtAuthenticationProvider extends DaoAuthenticationProvider {

    public JwtAuthenticationProvider(UserDetailsService userDetailsService) {
        setUserDetailsService(userDetailsService);
        setPasswordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 可以在此处覆写整个登录认证逻辑
        return super.authenticate(authentication);
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        String password = null;
        // 可以在此处覆写密码验证逻辑
        if (authentication.getCredentials() == null) {
            this.logger.debug("Authentication failed: no credentials provided");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        } else {
            String presentedPassword = authentication.getCredentials().toString();
            try {
                password = MD5Util.encrypt(presentedPassword);
            } catch (Exception e) {
                e.printStackTrace();
            }
                if (!password.equals(userDetails.getPassword())) {
                    this.logger.debug("Authentication failed: password does not match stored value");
                    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }
        }
//        super.additionalAuthenticationChecks(userDetails, authentication);
    }

}
登录认证检查过滤器
package com.rkhd.ienterprise.apps.bi.security;

import com.rkhd.ienterprise.apps.bi.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


public class JwtAuthenticationFilter extends BasicAuthenticationFilter {

    @Autowired
    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 获取token, 并检查登录状态
        SecurityUtils.checkAuthentication(request);
        chain.doFilter(request, response);
    }

}

自定义令牌对象:
package com.rkhd.ienterprise.apps.bi.security;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;


public class JwtAuthenticatioToken extends UsernamePasswordAuthenticationToken {

    private static final long serialVersionUID = 1L;

    private String token;

    public JwtAuthenticatioToken(Object principal, Object credentials){
        super(principal, credentials);
    }

    public JwtAuthenticatioToken(Object principal, Object credentials, String token){
        super(principal, credentials);
        this.token = token;
    }

    public JwtAuthenticatioToken(Object principal, Object credentials, Collection authorities, String token) {
        super(principal, credentials, authorities);
        this.token = token;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public static long getSerialversionuid() {
        return serialVersionUID;
    }

}

用户登录认证信息查询
package com.rkhd.ienterprise.apps.bi.security;


import com.rkhd.ienterprise.apps.bi.dto.BiUser;
import com.rkhd.ienterprise.apps.bi.exception.ManageException;
import com.rkhd.ienterprise.apps.bi.service.impl.BoardServiceImpl;
import com.rkhd.ienterprise.apps.bi.service.user.UserService;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static com.rkhd.ienterprise.apps.bi.utils.BiResultEnum.BOARD_SAVE_DB_ERROR_2;


@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    private static final Logger LOG = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
    @Autowired
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        BiUser user = null;
        Set roles = null;
        try {
            user = userService.getUsers(username);
            if (user == null) {
                throw new UsernameNotFoundException("该用户不存在");
            }
        } catch (ManageException e) {
            LOG.error(ExceptionUtils.getStackTrace(e));
        }

        // 用户权限列表,根据用户拥有的权限标识与如 @PreAuthorize("hasAuthority('sys:menu:view')") 标注的接口对比,决定是否可以调用接口
//        Set permissions = biUserService.findPermissions(username);

        try {
            roles = userService.getRoles(user.getId());
        } catch (ManageException e) {
            e.printStackTrace();
        }
        List grantedAuthorities = roles.stream().map(GrantedAuthorityImpl::new).collect(Collectors.toList());
        return new JwtUserDetails(username, user.getPassword(), grantedAuthorities);
    }
}
权限封装和安全用户模型 根据角色赋予不同的功能
package com.rkhd.ienterprise.apps.bi.security;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.util.Collection;


public class JwtUserDetails extends User {

    private static final long serialVersionUID = 1L;

    public JwtUserDetails(String username, String password, Collection authorities) {
        this(username, password, true, true, true, true, authorities);
    }

    public JwtUserDetails(String username, String password, boolean enabled, boolean accountNonExpired,
                          boolean credentialsNonExpired, boolean accountNonLocked, Collection authorities) {
        super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
    }

}

package com.rkhd.ienterprise.apps.bi.security;

import org.springframework.security.core.GrantedAuthority;


public class GrantedAuthorityImpl implements GrantedAuthority {

    private static final long serialVersionUID = 1L;

    private String authority;

    public GrantedAuthorityImpl(String authority) {
        this.authority = authority;
    }

    public void setAuthority(String authority) {
        this.authority = authority;
    }

    @Override
    public String getAuthority() {
        return this.authority;
    }
}

3.1 UserDetailsService

接口。该接口只提供了一个方法:

该方法很容易理解:通过用户名来加载用户。这个方法主要用于从系统数据中查询并加载具体的用户到Spring Security中。

3.2 UserDetails

从上面可以知道最终交给Spring Security的是。该接口是提供用户信息的核心接口。该接口实现仅仅存储用户的信息。后续会将该接口提供的用户信息封装到认证对象中去。默认提供了:

用户的权限集, 默认需要添加前缀

用户的加密后的密码, 不加密会使用前缀

应用内唯一的用户名

账户是否过期

账户是否锁定

凭证是否过期

用户是否可用

如果以上的信息满足不了你使用,你可以自行实现扩展以存储更多的用户信息。比如用户的邮箱、手机号等等。通常我们使用其实现类:

该类内置一个建造器会很方便地帮助我们构建对象,后面我们会用到它。

SpringSecurity-1-UserDetails接口

这个接口是我们自己用来定义用户表的结构的

SpringSecurity自己的用户信息只包含了Username,password,roles,假如我希望用户的实体类中还有性别sex字段,那么就没有办法了,所以SpringSecurity提供了UserDetails接口,我们可以自己新建一个包含sex字段的类,然后该类implements UserDetails接口,就可以获取我们说的这个sex字段

UserDetails实例是通过UserDetailsService接口的loadUserByUsername方法返回的,可以参考这篇文章创建自己的UserDetails(本文中包含sex的那个类)

UserDetails接口中有一个getAuthorities方法,这个方法返回的是权限,但是我们返回的权限必须带有“ROLE_”开头才可以,spring会自己截取ROLE_后边的字符串,也就是说,比如:我的权限叫ADMIN,那么,我返回告诉spring security的时候,必须告诉他权限是ROLE_ADMIN,这样spring security才会认为权限是ADMIN

示例代码:

假设一个用户拥有两个权限,一个叫权限1,一个叫权限2,下面的代码演示了如何使用UserDetails接口获取这两个权限,其本质就是实现getAuthorities()方法

public MyUser implements UserDetails{
		// 这个GrantedAuthority是什么呢?其实它就是权限(Role),
		// 因为本例中该用户有两个权限,所以这个Collection的长度是2
		@Override
		public Collection getAuthorities(){
				String role1="ROLE_权限1";
				String role2="ROLE_权限2";
				GrantedAuthority authority1=new SimpleGrantedAuthority(role1);
				GrantedAuthority authority2=new SimpleGrantedAuthority(role2);
				List list=new ArrayList<>();
				list.add(authority1);
				list.add(authority2);
				return list;				
		}
}

根据UserDetails接口,我们知道spring更推荐将权限用逗号隔开的方式存到数据库user表中,而不推荐使用1对多的方式来存储权限(单独的一个user表和权限表,然后使用一个关系表将两个表关联起来)

参考链接 

Spring Boot:整合Spring Security - 朝雨忆轻尘 - 博客园

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/571318.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号