• Spring Security


    Spring Security

    Spring家族的安全管理框架,竞品是Shiro。

    虽然Security功能比Shiro强大,但Spring Boot出现之前,Security的整合比较麻烦,使得大部分项目选择使用Shiro。

    Spring Boot对于Spring Security提供了自动化配置方案,常常配合使用。

    Bcrypt加密

    BCryptPasswordEncoder类实现了Bcrypt加密。

    Bcrypt加密使用单向hash算法,每次加密结果不一样,因此无解密功能,即使数据库泄漏也很难破解密码。

    BCryptPasswordEncoder可以加密(encode)和匹配(matches)。

    实验

    添加依赖

    <!--security -->
    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    

    编写配置类

    添加Spring Security依赖后,访问任何URL都会进入自带的Login Page页面。

    为了查看BCrypt加密效果,需要添加一个配置类,使得所有地址都可以匿名访问。

    package com.ah.security;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.*;
    
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    	@Override
    	protected void configure(HttpSecurity http) throws Exception {
    		// authorize:授权
    		// authenticated:验证
    		// csrf跨站点请求伪造(Cross—Site Request Forgery)
    		http.authorizeRequests().antMatchers("/**").permitAll()
    		.anyRequest().authenticated().and().csrf().disable();
    	}
    }
    

    编写启动类,配置BCryptPasswordEncoder的@Bean

    package com.ah;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Bean;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    
    @SpringBootApplication
    public class Application {
    	public static void main(String[] args) {
    		SpringApplication.run(Application.class, args);
    	}
    
    	@Bean
    	public BCryptPasswordEncoder encoder() {
    		return new BCryptPasswordEncoder();
    	}
    }
    

    测试加密(regist)和密码匹配(login)

    package com.ah.security;
    
    import javax.servlet.http.HttpServletRequest;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.web.bind.annotation.*;
    import io.jsonwebtoken.Claims;
    
    @RestController
    public class UserController {
    	@Autowired
    	BCryptPasswordEncoder encoder;
    	static String DUMMY_DB_PWD = "";
    
    	@GetMapping("/BCrypt/regist/{pwd}")
    	public String regist(@PathVariable String pwd) {
    		pwd = encoder.encode(pwd);
    		// DUMMY:存入数据库
    		DUMMY_DB_PWD = pwd;
    		return "pwd = " + DUMMY_DB_PWD;
    		// http://127.0.0.1:8080/BCrypt/regist/123
    	}
    
    	@GetMapping("/BCrypt/login/{pwd}")
    	public String login(@PathVariable String pwd) {
    		// DUMMU:从数据库根据用户名,取登录用户信息
    		if (encoder.matches(pwd, DUMMY_DB_PWD)) {
    			return "login Success";
    		} else {
    			return "login Fail";
    		}
    		// http://127.0.0.1:8080/BCrypt/login/123
    	}
    }
    

    JWT

    Json Web Token,是一种规范,定义了基于Json和Token的身份验证机制

    JWT使得信息可以在客户端和服务器之间可靠传递,适用于分布式站点的单点登录。

    JWT基于Token验证,在服务端不存储用户的登录记录,流程如下:

    1. 客户端发送登录请求
    2. 服务器验证成功后,发送一个Token给客户端。
    3. 客户端存储Token,可以存在Cookie里。
    4. 客户端每次向服务器发送请求时,都要携带该Token。
    5. 服务器收到请求,对Token进行验证,验证成功就继续处理。

    Token机制的好处:

    • 支持跨域访问(Cookie不支持)
    • 支持多平台(移动平台支持Cookie)
    • 不用考虑scrf(跨站点请求伪造)。
    • 无状态,即服务器不存储登录信息
    • 解耦
    • 性能:相对session存储登录信息的形式,JWT性能更好。
    • 标准化:JWT已被多种技术所支持(Java、.NET、Python、PHP、Ruby等)

    JWT是字符串,由三部分组成:头部header、载荷playload、签名signature。如:

    eyJhbGciOiJIUzI1NiJ9.
    eyJqdGkiOiIwMDciLCJzdWIiOiLpgqblvrciLCJpYXQiOjE1OTEyMDE0MjMsImV4cCI6MTU5MTIwNTAyMywicm9sZSI6ImFkbWluIn0.
    Rd0IOiEoLodeZDDikS7to4JakThtYOCUtCJ3alCHjQM
    
    • header:描述基本信息,如类型或签名算法等。

    • playload:载荷,存放有效信息,包括标准声明、公共声明、私有声明。

    • signature:签名。

    JJWT实现JWT

    <!-- JJWT:Token认证 -->
    <dependency>
    	<groupId>io.jsonwebtoken</groupId>
    	<artifactId>jjwt</artifactId>
    	<version>0.9.1</version>
    </dependency>
    <!-- 如果测试出错,则加入此包 -->
    <dependency>
    	<groupId>javax.xml.bind</groupId>
    	<artifactId>jaxb-api</artifactId>
    </dependency>
    

    编写测试类(其实就是工具类,后面用)

    • 新建JWT字符串
      • 注意自定义属性(claim:声明)
    • 解析JWT字符串
    package com.ah.security;
    import java.util.Date;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Configuration;
    import io.jsonwebtoken.*;
    @Configuration
    public class JWTUtil {
    	@Value("andy")
    	private String key;
    	@Value("3600000") // 60min*60s*1000ms
    	private long ttl;// time to live,生存时间
    
    	public String createJWT(String _id, String _subject, String _role) {
    		JwtBuilder builder = Jwts.builder();
    		builder.setId(_id);
    		builder.setSubject(_subject);
    		builder.setIssuedAt(new Date());// 发行时间
    		builder.signWith(SignatureAlgorithm.HS256, key);// 签名
    		long nowMillis = System.currentTimeMillis();
    		builder.setExpiration(new Date(nowMillis + ttl));// 失效时间
    		// 自定义属性(claim:声明)
    		builder.claim("role", _role);
    		String strJWT = builder.compact();// 把…紧压在一起
    		return strJWT;
    	}
    
    	public Claims parserJWT(String strJWT) {
    		JwtParser parser = Jwts.parser();
    		parser.setSigningKey(key);
    		Jws<Claims> parseClaimsJws = parser.parseClaimsJws(strJWT);
    		Claims body = parseClaimsJws.getBody();
    		return body;
    	}
    
    	public static void main(String[] args) {
    		JWTUtil jwt = new JWTUtil();
    		jwt.key = "andy";
    		jwt.ttl = 3600000;
    		String str = jwt.createJWT("007", "邦德", "admin");
    		System.out.println(str);
    		Claims body = jwt.parserJWT(str);
    		System.out.println("Id=" + body.getId());
    		System.out.println("Subject=" + body.getSubject());
    		System.out.println("IssuedAt=" + body.getIssuedAt());
    		System.out.println("Expiration=" + body.getExpiration());
    		System.out.println("role=" + body.get("role"));
    	}
    }
    

    应用:鉴权

    • 使用拦截器对用户进行鉴权。
    • 登录页面不拦截。
    • 用户登录,获得token。
    • 用户发送一个Delete请求,该请求被鉴权。

    拦截器

    package com.ah.security.interceptor;
    import javax.servlet.http.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.HandlerInterceptor;
    import com.ah.security.JWTUtil;
    import io.jsonwebtoken.Claims;
    
    @Component
    public class JWTInterceptor implements HandlerInterceptor {
    
    	@Autowired
    	private JWTUtil jwtUtil;
    
    	@Override
    	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    			throws Exception {
    		System.out.println("---preHandle---拦截---" + request.getRequestURI());
    
    		String jwt = request.getHeader("Authorization");
    		if (jwt != null) {
    			System.out.println(jwt);
    			// 解析JWT
    			Claims claims = jwtUtil.parserJWT(jwt);
    			// 根据role,设置属性
    			String role = (String) claims.get("role");
    			if ("admin".equalsIgnoreCase(role)) {
    				request.setAttribute("role_admin", claims);
    			} else if ("user".equalsIgnoreCase(role)) {
    				request.setAttribute("role_user", claims);
    			}
    		}
    		return true;
    	}
    }
    

    拦截器配置类

    package com.ah.security.interceptor;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
    
    @Configuration
    public class ApplicationConfig extends WebMvcConfigurationSupport {
    	@Autowired
    	private JWTInterceptor jwtInterceptor;
    
    	@Override
    	protected void addInterceptors(InterceptorRegistry registry) {
    		InterceptorRegistration registration = registry.addInterceptor(jwtInterceptor);
    		registration.addPathPatterns("/**");// 全部拦截
    		registration.excludePathPatterns("/**/login/**");// 不拦截
    	}
    }
    

    配置器中加两个方法

    	@Autowired
    	private JWTUtil jwtUtil;
    
    	@GetMapping("/jwt/login/{pwd}")
    	public String jwt_login(@PathVariable String pwd) {
    		// DUMMU:从数据库根据用户名,取登录用户信息
    		DUMMY_DB_PWD = encoder.encode(pwd);
    		if (encoder.matches(pwd, DUMMY_DB_PWD)) {
    			System.out.println("登录成功");
    			// 登录成功后,返回给用户jwt(这里直接返回字符串,但实际项目中一般会封装到结果对象中)
    			String jwt = jwtUtil.createJWT("DUMMY_ID", pwd, "admin");
    			return jwt;
    		} else {
    			System.out.println("登录失败");
    			return "login Fail(JWT)";
    		}
    		// http://127.0.0.1:8080/jwt/login/123
    	}
    
    	@GetMapping("/jwt/delete")
    	public String adminDelete(HttpServletRequest request) {
    		Claims claims = (Claims) request.getAttribute("role_admin");
    		if (claims == null) {
    			return "Permission denied";
    		}
    		// do something
    		return "Delete Success";
    		// http://127.0.0.1:8080/jwt/delete
            // postman测试,Headers中新建Authorization,value=jwt
    	}
    
  • 相关阅读:
    C# 中字符串转换成日期
    c#中退出WinForm程序包括有很多方法,如:this.Close(); Application.Exit();Application.ExitThread(); System.Environment.Exit(0);
    c#获取程序版本号
    分分钟用上C#中的委托和事件
    【转载】C# 中的委托和事件(详解:简单易懂的讲解)
    C#什么时候需要使用构造函数
    15、生命周期-BeanPostProcessor-后置处理器
    13、生命周期-InitializingBean和DisposableBean
    11、组件注册-使用FactoryBean注册组件
    12、生命周期-@Bean指定初始化和销毁方法
  • 原文地址:https://www.cnblogs.com/tigerlion/p/13042632.html
Copyright © 2020-2023  润新知