目录
认证授权概念
1.1 什么是认证
1.2 什么是授权
权限数据类型
常见的认证方式
一:Cookie-Session
二: jwt令牌无状态认证
JWT
一.JWT简介
二.JWT组成
三.JWT使用
1.导入jwt依赖
2.生成JWT令牌
3.JWT令牌校验
四.JWT在前端保存方案
SpringSecurity
SpringSecurity简介
SpringSecurity基本使用
controller
一.导入依赖
二.SpringSecurity自定义认证配置
三.SpringSecurity自定义授权配置
四.使用注解自定义授权
1.开启SpringSecurity注解支持
2.添加注解
认证授权概念
1.1 什么是认证
在互联网中,我们每天都会使用到各种各样的APP和网站,在使用过程中通常还会遇到需要注册登录的情况,输入你的用户名和密码才能正常使用,也就是说成为这个应用的合法身份才可以访问应用的资源,这个过程就是认证。认证是为了保护系统的隐私数据与资源,用户的身份合法方可访问该系统的资源。
当然认证的方式有很多,常见的账号密码登录,手机验证码登录,指纹登录,刷脸登录等等。
简单说: 认证就是让系统知道我们是谁。
1.2 什么是授权
认证是为了保护身份的合法性,授权则是为了更细粒度的对数据进行划分,授权是在认证通过的前提下发生的。控制不同的用户能够访问不同的资源。
**授权是用户认证通过后根据用户的权限来控制用户访问资源的过程**,拥有资源的访问权限则正常访问,没有权限则拒绝访问。
权限数据类型
授权过程中,我们需要知道如何对用户访问的资源进行控制,需要了解一些简单的授权数据模型。
授权可以非常简单的理解成谁(Who)对什么资源(What)进行怎么样(How)的操作。
名词 | 含义 | 备注 |
---|---|---|
Who | 主体(Subject) | 一般指用户,也可以是应用程序 |
What | 资源(Resource) | 例如商品信息,订单信息,页面按钮或程序中的接口等信息 |
How | 权限(Permission) | 规定了用户或程序对资源操作的许可。例如普通用户只能查看订单,管理员可修改或删除订单,这是因为普通用户和管理员用户对订单资源的操作权限不一样。 |
常见的认证方式
一:Cookie-Session
早期互联网以 web 为主,客户端是浏览器,所以 Cookie-Session 方式最那时候最常用的方式,直到现在,一些 web 网站依然用这种方式做认证;
认证过程大致如下:
A. 用户输入用户名、密码或者用短信验证码方式登录系统;
B. 服务端验证后,创建一个 Session 记录用户登录信息 ,并且将 SessionID 存到 cookie,响应回浏览器;
C. 下次客户端再发起请求,自动带上 cookie 信息,服务端通过 cookie 获取 Session 信息进行校验;
弊端
- 只能在 web 场景下使用,如果是 APP 中,不能使用 cookie 的情况下就不能用了;
- 即使能在 web 场景下使用,也要考虑跨域问题,因为 cookie 不能跨域;(域名或者ip一致,端口号一致,协议要一致)
- cookie 存在 CSRF(跨站请求伪造)的风险;
- 如果是分布式服务,需要考虑 Session 同步(同步)问题;
- session-cookie机制是有状态的方式(后端保存主题的用户信息-浪费后端服务器内存)
二: jwt令牌无状态认证
JSON Web Token(JWT-字符串)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
**认证过程: **
A. 依然是用户登录系统;
B. 服务端验证,并通过指定的算法生成令牌返回给客户端;
C. 客户端(浏览器)拿到返回的 Token,存储到 local storage(关闭浏览器后,token不会消失)/session Storate(关闭浏览器后,token会消失)/Cookie中;
D. 下次客户端再次发起请求,将 Token 附加到 header 中;
E. 服务端获取 header 中的 Token ,通过相同的算法对 Token 进行验证,如果验证结果相同,则说明这个请求是正常的,没有被篡改。这个过程可以完全不涉及到查询 Redis 或其他存储;
优点
A. 使用 json 作为数据传输,有广泛的通用型,并且体积小,便于传输;
B. 不需要在服务器端保存相关信息,节省内存资源的开销;
C. jwt 载荷部分可以存储业务相关的信息(非敏感的),例如用户信息、角色等;
JWT
一.JWT简介
JSON Web Token(JWT)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。该token被设计为紧凑且安全的,特别适用于前后端无状态认证的场景。
二.JWT组成
- 头部(Header)(非敏感)
- 头部用于描述关于该JWT的最基本的信息,例如数据类型以及签名所用的算法等,本质是一个JSON格式对象;
- 举例说明
- {"typ":"JWT","alg":"HS256"} 解释:在头部指明了签名算法是HS256算法,整个JSON对象被BASE64编码形成JWT头部字符串信息;
- BASE64编码详见:https://tool.oschina.net/encrypt,编码后的字符串:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
- 值得注意的是BASE64不是加密算法,可进行正向编码和反向解码处理;
- 载荷(playload)(非敏感数据)
- 载荷就是存放有效信息的地方,该部分的信息是可以自定义的;
- 载荷payload格式:{"sub":"1234567890","name":"John Doe","admin":true}
- 载荷相关的JSON对象经过BASE64编码形成JWT第二部分:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG7CoERvZSIsImFkbWluIjp0cnVlfQ==
- 签证(signature)
- jwt的第三部分是一个签证信息,这个签证信息由三部分组成:签名算法( header (base64后的).payload (base64后的) . secret )
- 这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret秘钥组合加密,然后就构成了jwt的第三部分:TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
最后将这三部分用●连接成一个完整的字符串,构成了最终的jwt:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG7CoERvZSIsImFkbWluIjp0cnVlfQ==.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
三.JWT使用
1.导入jwt依赖
<dependencies><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>
</dependencies>
2.生成JWT令牌
java">@Test
public void testGenerate(){String compact = Jwts.builder().setId(UUID.randomUUID().toString())//设置唯一标识.setSubject("JRZS") //设置主题.claim("name", "nineclock") //自定义信息.claim("age", 88) //自定义信息.setExpiration(new Date()) //设置过期时间.setIssuedAt(new Date()) //令牌签发时间.signWith(SignatureAlgorithm.HS256, "hhH")//签名算法, 秘钥.compact();System.out.println(compact);
}
3.JWT令牌校验
java">@Test
public void testVerify(){String jwt = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI5MzljNjU4MC0yMTQyLTRlOWEtYjcxOC0yNzlmNzRhODVmNDMiLCJzdWIiOiJOSU5FQ0xPQ0siLCJuYW1lIjoibmluZWNsb2NrIiwiYWdlIjo4OCwiaWF0IjoxNjE3MDMxMjUxfQ.J-4kjEgyn-Gkh0ZuivUCevrzDXt0K9bAyF76rn1BfUs";Claims claims = Jwts.parser().setSigningKey("hhh").parseClaimsJws(jwt).getBody();System.out.println(claims);
}
当我们对令牌进行任何部分(header , payload , signature)任何部分进行篡改, 都会造成令牌解析失败 ;
四.JWT在前端保存方案
后端基于JWT生成的Token信息在前端有如下保存方式:
- LocalStorage(浏览器关闭后,token不会消失)
- SessionStorage(浏览器关闭后,token会消失)
- cookie
- 页面中
- 其它
以LocalStorage为例:
javascript"><script>//保存信息localStorage.setItem("token", "xxx");//获取信息alert(localStorage.getItem("token"))//删除信息localStorage.removeItem("token");
</script>
SpringSecurity
SpringSecurity简介
- Spring Security是基于Spring的安全框架,提供了包含认证和授权的落地方案;
- Spring Security底层充分利用了Spring IOC和AOP功能,为企业应用系统提供了声明式安全访问控制解决方案;
- SpringSecurity可在Web请求级别和方法调用级别处理身份认证和授权,为应用系统提供声明式的安全访问控制功能;
SpringSecurity基本使用
controller
java">@RestController
public class UserController {@GetMapping("/hello")public String hello(){return "hello security";}@GetMapping("/say")public String say(){return "say security";}@GetMapping("/register")public String register(){return "register security";}
}
一.导入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
二.SpringSecurity自定义认证配置
声明配置类,定义用户名密码信息
java">@Configuration
@EnableWebSecurity//开启web安全设置生效
public class SecurityConfig extends WebSecurityConfigurerAdapter {//使用该方法创建用户,并为用户赋予权限/*** 构建认证服务,并将对象注入spring IOC容器,用户登录时,会调用该服务进行用户合法信息认证*/@Beanprotected UserDetailsService userDetailsService() {//从内存获取用户认证信息的服务类(了解)后期用户的信息要从表中获取InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();//创建用户,这里自定义用户,以后通过数据库进行用户创建UserDetails u1= User.withUsername("hhh").password("{noop}123456")//{noop}-->表示no operation 就是直接明文比对.authorities("P1","ROLE_SELECT")//用户的权限信息.build();UserDetails u2=User.withUsername("aaa").password("{noop}123456").authorities("P2","ROLE_ADMIN").build();//构建用户inMemoryUserDetailsManager.createUser(u1);inMemoryUserDetailsManager.createUser(u2);return inMemoryUserDetailsManager;}
}
说明:
1.在userDetailsService()方法中 返回了一个UserDetailsService对象给spring容器管理,当用户发生登录认证行为时,Spring Security底层会自动调用UserDetailsService类型bean提供的用户信息进行合法比对,如果比对成功则资源放行,否则就认证失败;
2.当前暂时使用InMemoryUserDetailsManager实现类,后续我们也可手动实现UserDetailsService接口,做最大程度的自定义;
可以发现,在访问所有的资源时,必须进行认证才能访问
三.SpringSecurity自定义授权配置
给每个路径分配权限 ,访问某一个路径时,需要访问某一个路径时,需要进行用户认证,只有这个用户拥有访问这个路径的权限时,才能访问这个路径
permitAll()这个方法不用进行用户认证,可以直接访问
java"> @Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin()//开启默认form表单登录方式.and().logout()//登出用默认的路径登出 /logout.permitAll()//允许所有的用户访问登录或者登出的路径.and().csrf().disable()//启用CSRF,防止CSRF攻击.authorizeRequests()//授权方法,该方法后有若干子方法进行不同的授权规则处理//允许所有账户都可访问(不登录即可访问),同时可指定多个路径.antMatchers("/register").permitAll()//允许所有的用户访问.antMatchers("/hello").hasAuthority("P1") //具有P5权限才可以访问.antMatchers("/say").hasRole("SELECT") //具有ROLE_ADMIN 角色才可以访问,会自动加上ROLE_.antMatchers("/aa","/bb").hasAnyAuthority("P1","ROLE_SELECT")//有任意一个权限都可以访问.antMatchers("/aa","/bb").hasAnyRole("SELECT","ADMIN)//有任意一个权限都可以访问.antMatchers("/aa","/bb").hasIpAddress("192.168.xxx.xxx")//必须是192.168.地址才能访问.antMatchers("/aa","/bb").denyAll()//任何用户都不可以访问.anyRequest().authenticated(); //除了上边配置的请求资源,其它资源都必须授权才能访问}
四.使用注解自定义授权
1.开启SpringSecurity注解支持
@EnableGlobalMethodSecurity(prePostEnabled = true)
java">@Configuration
@EnableWebSecurity//开启web安全设置生效
//开启SpringSecurity相关注解支持
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {}
2.添加注解
java">@RestController
public class UserController {//拥有ROLE_ADMIN权限的用户才能访问此接口@PreAuthorize("hasRole('ADMIN')")@GetMapping("/hello")public String hello(){return "hello security";}//拥有ROLE_SELECT权限的用户才能访问此接口@PreAuthorize("hasRole('SELECT')")@GetMapping("/say")public String say(){return "say security";}@PermitAll//任何用户都可以访问此接口,不需要进行认证@GetMapping("/register")public String register(){return "register security";}
}