Spring Security框架
Spring Security框架
关于用户身份认证与授权
Spring Security是用于解决认证与授权的框架。
添加依赖
<!-- Spring Boot Security:处理认证与授权 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
启动项目,在启动的日志中,可以看到类似以下内容:
Using generated security password: 2abb9119-b5bb-4de9-8584-9f893e4a5a92
Spring Security有默认登录的账号和密码(以上提示的值),密码是随机的,每次启动项目都会不同。
Spring Security默认要求所有的请求都是必须先登录才允许的访问,可以使用默认的用户名user
和自动生成的随机密码来登录。在测试登录时,在浏览器访问当前主机的任意网址都可以(包括不存在的资源),会自动跳转到登录页(是由Spring Security提供的,默认的URL是:http://localhost:8080/login),当登录成功后,会自动跳转到此前访问的URL(跳转登录页之前的URL),另外,还可以通过 http://localhost:8080/logout 退出登录。
Spring Security的依赖项中包括了Bcrypt算法的工具类,Bcrypt是一款非常优秀的密码加密工具,适用于对需要存储下来的密码进行加密处理。
测试例 :
public class BcryptPasswordEncoderTests {private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();@Testpublic void testEncode() {// 原文相同的情况,每次加密得到的密文都不同for (int i = 0; i < 10; i++) {String rawPassword = "123456";String encodedPassword = passwordEncoder.encode(rawPassword);System.out.println("rawPassword = " + rawPassword);System.out.println("encodedPassword = " + encodedPassword);}// rawPassword = 123456// encodedPassword = $2a$10$HWuJ9WgPazrwg9.isaae4u7XdP7ohH7LetDwdlTWuPC4ZAvG.Uc7W}@Testpublic void testMatches() {String rawPassword = "123456";String encodedPassword = "$2a$10$hI4wweFOGJ7FMduSmCjNBexbKFOjYMWl8hkug0n0k1LNR5vEyhhMW";boolean matchResult = passwordEncoder.matches(rawPassword, encodedPassword);System.out.println("match result : " + matchResult);}}
Spring Security的认证机制中包含:当客户端提交登录后,会自动调用UserDetailsService
接口(Spring Security定义的)的实现类对象中的UserDetails loadUserByUsername(String username)
方法(根据用户名加载用户数据),将得到UserDetails
类型的对象,此对象中应该至少包括此用户名对应的密码、权限等信息,接下来,Spring Security会自动完成密码的对比,并确定此次客户端提交的信息是否允许登录!类似于:
// Spring Security的行为
UserDetails userDetails = userDetailsService.loadUserByUsername("chengheng");
// Spring Security将从userDetails中获取密码,用于验证客户端提交的密码,判断是否匹配
所以,要实现Spring Security通过数据库的数据来验证用户名与密码(而不是采用默认的user
用户名和随机的密码),则在passport
包下创建security.UserDetailsServiceImpl
类,实现UserDetailsService
接口,并重写接口中的抽象方法:
@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate AdminMapper adminMapper;@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {System.out.println("根据用户名查询尝试登录的管理员信息,用户名=" + s);/*通过用户名从缓存中获取用户信息*/AdminLoginVO admin = adminMapper.getLoginInfoByUsername(s);System.out.println("通过持久层进行查询,结果=" + admin);if (admin == null) {System.out.println("根据用户名没有查询到有效的管理员数据,将抛出异常");throw new BadCredentialsException("登录失败,用户名不存在!");}System.out.println("查询到匹配的管理员数据,需要将此数据转换为UserDetails并返回");UserDetails userDetails = User.builder().username(admin.getUsername()).password(admin.getPassword())/* accountExpired 账号是否过期 */.accountExpired(false)/* accountLocked 账号是否锁定 */.accountLocked(false)/* disabled 账号是否禁用 */.disabled(admin.getIsEnable() != 1)/* credentialsExpired 证书是否过期 */.credentialsExpired(false)/* authorities 用户权限 */.authorities(admin.getPermissions().toArray(new String[] {})).build();System.out.println("转换得到UserDetails=" + userDetails);return userDetails;}}
完成后,再配置密码加密器即可:
@Configuration
public class SecurityConfiguration {@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}}
重启项目,可以发现在启动过程中不再生成随机的密码值,在浏览器上访问此项目的任何URL,进入登录页,即可使用数据库中的管理员数据进行登录。
在Spring Security,默认使用Session机制存储成功登录的用户信息(因为HTTP协议是无状态协议,并不保存客户端的任何信息,所以,同一个客户端的多次访问,对于服务器而言,等效于多个不同的客户端各访问一次,为了保存用户信息,使得服务器端能够识别客户端的身份,必须采取某种机制),当下,更推荐使用Token或相关技术(例如JWT)来解决识别用户身份的问题。
JWT
JWT = JSON Web Token,它是通过JSON格式组织必要的数据,将数据记录在票据(Token)上,并且,结合一定的算法,使得这些数据会被加密,然后在网络上传输,服务器端收到此数据后,会先对此数据进行解密,从而得到票据上记录的数据(JSON数据),从而识别用户的身份,或者处理相关的数据。
其实,在客户端第1次访问服务器端时,是“空着手”访问的,不会携带任何票据数据,当服务器进行响应时,会将JWT响应到客户端,客户端从第2次访问开始,每次都应该携带JWT发起请求,则服务器都会收到请求中的JWT并进行处理。
要使用JWT,需要添加相关的依赖项,可以实现生成JWT、解析JWT的框架较多,目前,主流的JWT框架可以是jjwt
:
JWT的组成部分:Header(头),Payload(载荷),Signature(签名)
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
则在根项目中管理以上依赖,并在csmall-passport
中添加以上依赖。
测试使用JWT:
public class JwtTests {// 密钥String secretKey = "fgfdsfadsfadsafdsafdsfadsfadsfdsafdasfdsafdsafdsafds4rttrefds";@Testpublic void testGenerateJwt() {// ClaimsMap<String, Object> claims = new HashMap<>();claims.put("id", 9527);claims.put("name", "家丁");// JWT的组成部分:Header(头),Payload(载荷),Signature(签名)String jwt = Jwts.builder()// Header:指定算法与当前数据类型// 格式为: { "alg": 算法, "typ": "jwt" }.setHeaderParam(Header.CONTENT_TYPE, "HS256").setHeaderParam(Header.TYPE, Header.JWT_TYPE)// Payload:通常包含Claims(自定义数据)和过期时间.setClaims(claims).setExpiration(new Date(System.currentTimeMillis() + 5 * 60 * 1000))// Signature:由算法和密钥(secret key)这2部分组成.signWith(SignatureAlgorithm.HS256, secretKey)// 打包生成.compact();// eyJjdHkiOiJIUzI1NiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0.eyJuYW1lIjoi5pif5pifIiwiaWQiOjk1MjcsImV4cCI6MTY1NTM2NzMwMn0.qBBHearv8iHPNjtDGtO2ci_-KAL4CALHnwzaG_ljsQgSystem.out.println(jwt);}@Testpublic void testParseJwt() {String jwt = "eyJjdHkiOiJIUzI1NiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0.eyJuYW1lIjoi5pif5pifIiwiaWQiOjk1MjcsImV4cCI6MTY1NTM2NzMwMn0.qBBHearv8iHPNjtDGtO2ci_-KAL4CALHnwzaG_ljsQg";Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();Object id = claims.get("id");Object name = claims.get("name");System.out.println("id=" + id);System.out.println("name=" + name);}}
当JWT数据过期时,异常信息例如:
io.jsonwebtoken.ExpiredJwtException: JWT expired at 2022-06-16T15:47:57Z. Current time: 2022-06-16T16:08:32Z, a difference of 1235869 milliseconds. Allowed clock skew: 0 milliseconds.
当JWT解析失败(数据有误)时,异常信息例如:
io.jsonwebtoken.MalformedJwtException: Unable to read JSON value: {"cty"�"HS256","typ":"JWT","alg":"HS256"}
当生成JWT和解析JWT的密钥不一致时,异常信息例如:
io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
要在Spring Security中使用JWT,至少需要:
不能让Spring Security按照原有模式来处理登录(原有模式中,登录成功后,自动装用户信息存储到Session中,且跳转页面),需要
需要自动装配
AuthenticationManager
对象- 使得
SecurityConfiguration
配置类继承自WebSecurityConfigurerAdapter
类,重写其中的xx
方法,在此方法中直接调用父级方法即可,并在此方法上添加@Bean
注解
- 使得
创建
AdminLoginDTO
类,此类中应该包含用户登录时需要提交的用户名、密码创建
IAdminService
接口在
IAdminService
接口中添加登录的抽象方法String login(AdminLoginDTO adminLoginDTO);
创建
AdminServiceImpl
类,实现以上接口- 在实现过程中,调用
AuthenticationManager
实现认证,当认证成功后,生成JWT并返回
- 在实现过程中,调用
创建
AdminController
类,在类中处理登录请求在
SecurityConfiguration
中配置Spring Security,对特定的请求进行放行(默认所有请求都必须先登录)
配置代码
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity http) throws Exception {// 禁用防跨域攻击http.csrf().disable();// URL白名单String[] urls = {"/admins/login"};// 配置各请求路径的认证与授权http.authorizeRequests() // 请求需要授权才可以访问.antMatchers(urls) // 匹配一些路径.permitAll() // 允许直接访问(不需要经过认证和授权).anyRequest() // 匹配除了以上配置的其它请求.authenticated(); // 都需要认证}
}
@Data
public class AdminLoginDTO implements Serializable {private String username;private String password;
}
public interface IAdminService {String login(AdminLoginDTO adminLoginDTO);}
@Service
public class AdminServiceImpl implements IAdminService {@Autowiredprivate AuthenticationManager authenticationManager;@Overridepublic String login(AdminLoginDTO adminLoginDTO) {// 准备被认证数据Authentication authentication= new UsernamePasswordAuthenticationToken(adminLoginDTO.getUsername(), adminLoginDTO.getPassword());// 调用AuthenticationManager验证用户名与密码// 执行认证,如果此过程没有抛出异常,则表示认证通过,如果认证信息有误,将抛出异常authenticationManager.authenticate(authentication);// 如果程序可以执行到此处,则表示登录成功// 生成此用户数据的JWTString jwt = "This is a JWT."; // 临时return jwt;}
}
@RestController
@RequestMapping(value = "/admins", produces = "application/json; charset=utf-8")
public class AdminController {@Autowiredprivate IAdminService adminService;// http://localhost:8080/admins/login?username=root&password=123456@RequestMapping("/login")public String login(AdminLoginDTO adminLoginDTO) {String jwt = adminService.login(adminLoginDTO);return jwt;}
}
测试
完成以上配置后,可以通过 http://localhost:8080/admins/login?username=root&password=123456 这类URL测试登录,使用数据库中的用户名和密码进行尝试。
当通过以上URL进行访问时,其内部过程大概是:
- Spring Security的相关配置会进行URL的检查,来判断是否允许访问此路径
- 所以,需要在
SecurityConfiguration
中将以上路径设置为白名单 - 如果没有将以上路径配置到白名单,将直接跳转到登录页,因为默认所有请求都必须先登录
- 所以,需要在
- 由
AdminController
接收到请求后,调用了IAdminService
接口的实现类对象来处理登录IAdminService
接口的实现是AdminServiceImpl
- 在
AdminServiceImpl
中,调用了AuthenticationManager
处理登录的认证AuthenticationManager
对象调用authenticate()
方法进行登录处理- 内部实现中,会自动调用
UserDetailsService
实现对象的loadUserByUsername()
方法以获取用户信息,并自动完成后续的认证处理(例如验证密码是否正确),所以,在步骤中,具体执行的是UserDetailsServiceImpl
类中重写的方法,此方法返回了用户信息,Spring Security自动验证,如果失败(例如账号已禁用、密码错误等),会抛出异常
- 内部实现中,会自动调用
- 以上调用的
authenticate()
方法如果未抛出异常,可视为认证成功,即登录成功 - 当登录成功时,应该返回此用户的JWT数据
Spring Security + JWT
此前,在处理登录的业务中,当视为登录成功时,返回的字符串并不是JWT数据,则应该将此数据改为必要的JWT数据。
@Service
public class AdminServiceImpl implements IAdminService {// ===== 原有其它代码 =====/*** JWT数据的密钥*/private String secretKey = "fgfdsfadsfadsafdsafdsfadsfadsfdsafdasfdsafdsafdsafds4rttrefds";@Overridepublic String login(AdminLoginDTO adminLoginDTO) {// ===== 原有其它代码 =====// 如果程序可以执行到此处,则表示登录成功// 生成此用户数据的JWT// ClaimsUser user = (User) authenticate.getPrincipal();System.out.println("从认证结果中获取Principal=" + user.getClass().getName());Map<String, Object> claims = new HashMap<>();claims.put("username", user.getUsername());claims.put("permissions", user.getAuthorities());System.out.println("即将向JWT中写入数据=" + claims);// JWT的组成部分:Header(头),Payload(载荷),Signature(签名)String jwt = Jwts.builder()// Header:指定算法与当前数据类型// 格式为: { "alg": 算法, "typ": "jwt" }.setHeaderParam(Header.CONTENT_TYPE, "HS256").setHeaderParam(Header.TYPE, Header.JWT_TYPE)// Payload:通常包含Claims(自定义数据)和过期时间.setClaims(claims).setExpiration(new Date(System.currentTimeMillis() + 5 * 60 * 1000))// Signature:由算法和密钥(secret key)这2部分组成.signWith(SignatureAlgorithm.HS256, secretKey)// 打包生成.compact();// 返回JWT数据return jwt;}}
在控制器中,应该响应JSON格式的数据,所以,需要添加依赖包含JsonResult
类的依赖。将控制器中处理请求的方法的返回值类型改为JsonResult<String>
,并调整返回值:
// http://localhost:8080/admins/login?username=root&password=123456
@RequestMapping("/login")
public JsonResult<String> login(AdminLoginDTO adminLoginDTO) {String jwt = adminService.login(adminLoginDTO);return JsonResult.ok(jwt);
}
此时,重启项目,在浏览器中,使用正确的用户名和密码访问,响应的结果例如:
{"state":20000,"message":null,"data":"eyJjdHkiOiJIUzI1NiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0.eyJwZXJtaXNzaW9ucyI6W3siYXV0aG9yaXR5IjoiL2Ftcy9hZG1pbi9kZWxldGUifSx7ImF1dGhvcml0eSI6Ii9hbXMvYWRtaW4vcmVhZCJ9LHsiYXV0aG9yaXR5IjoiL2Ftcy9hZG1pbi91cGRhdGUifSx7ImF1dGhvcml0eSI6Ii9wbXMvcHJvZHVjdC9kZWxldGUifSx7ImF1dGhvcml0eSI6Ii9wbXMvcHJvZHVjdC9yZWFkIn0seyJhdXRob3JpdHkiOiIvcG1zL3Byb2R1Y3QvdXBkYXRlIn1dLCJleHAiOjE2NTU0MzQwMzcsInVzZXJuYW1lIjoicm9vdCJ9.8ZIfpxxjJlwNo-E3JhXwH4sZR0J5-FU-HAOMu1Tg-44"
}
注意:以上只是访问/admins/login
时会执行所编写的流程(发送用户名和密码,得到含JWT的结果),并不代表真正意义的实现了“登录”!
登录的流程应该是:
客户端提交用户名和密码到服务器端 >>> 服务器端认证成功后响应JWT >>> 客户端在后续的请求中都携带JWT >>> 服务器端验证JWT来决定是否允许访问。
为了便于体现“客户端在后续的请求中都携带JWT”的操作,可以在项目中添加使用Knife4j。
当使用Knife4j时,需要在白名单中添加相关的放行资源路径,否则,Knife4j的页面将无法使用:
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {// ===== 原有其它代码 =====@Overrideprotected void configure(HttpSecurity http) throws Exception {// ===== 原有其它代码 =====// URL白名单String[] urls = {"/admins/login","/doc.html", // 从本行开始,以下是新增"/**/*.js","/**/*.css","/swagger-resources","/v2/api-docs","/favicon.ico"};// ===== 原有其它代码 =====}
}
在后续的访问中,必须在请求中携带JWT数据, 服务器端才可以尝试解析此JWT数据,从而判断用户是否已登录或允许访问。
为了便于测试,在控制器中添加一个测试访问的请求配置:
// 以下是测试访问的请求
@GetMapping("/hello")
public String sayHello() {return "hello~~~";
}
由于以上 /admins/hello
路径并不在白名单中,如果直接访问,会出现403错误。
在规范的使用方式中,JWT数据必须携带在请求头(Request Header)的Authorization
属性中。
按照以上规范,则服务器端在每次接收到请求后,首先,就应该先判断请求头中是否存在Authorization
、Authorization
的值是否有效等操作,通常,是通过过滤器来实现以上检查的。
在passport
的根包下的security
包下创建JwtAuthenticationFilter
过滤器类,需要继承自OncePerRequestFilter
类:
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {System.out.println("JwtAuthenticationFilter.doFilterInternal()");}
}
所有的过滤器都必须注册后才可以使用,且同一个项目中允许存在多个过滤器,形成过滤器链,以上用于验证JWT的过滤器应该运行在Spring Security处理登录的过滤器之前,需要在自定义的SecurityConfiguration
中的configure()
方法中将以上自定义的过滤器注册在Spring Security的相关过滤器之前:
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {// 新增@Autowiredprivate JwtAuthenticationFilter jwtAuthenticationFilter;// ===== 原有其它代码 =====@Overrideprotected void configure(HttpSecurity http) throws Exception {// ===== 原有其它代码 =====// 注册处理JWT的过滤器// 此过滤器必须在Spring Security处理登录的过滤器之前http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);}
}
完成后,重启项目,无论对哪个路径发出请求,在控制台都可以看出输出了过滤器中的输出语句内容,并且,在浏览器将显示一片空白。
关于JwtAuthenticationFilter
,它需要实现:
- 尝试从请求头中获取JWT数据
- 如果无JWT数据,应该直接放行,Spring Security还会进行后续的处理,例如白名单的请求将允许访问,其它请求将禁止访问
- 如果存在JWT数据,应该尝试解析
- 如果解析失败,应该视为错误,可以要求客户端重新登录,客户端就可以得到新的、正确的JWT,客户端在下一次提交请求时,使用新的JWT即可正确访问
- 将解析得到的数据封装到
Authentication
对象中- Spring Security的上下文中存储的数据类型是
Authentication
类型
- Spring Security的上下文中存储的数据类型是
- 为避免存入1次后,Spring Security的上下文中始终存在
Authentication
,在此过滤器执行的第一时间,应该清除上下文中的数据
/*** JWT过滤器:从请求头的Authorization中获取JWT中存入的用户信息* 并添加到Spring Security的上下文中* 以致于Spring Security后续的组件(包括过滤器等)能从上下文中获取此用户的信息* 从而验证是否已经登录、是否具有权限等*/
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {/*** JWT数据的密钥*/private String secretKey = "fgfdsfadsfadsafdsafdsfadsfadsfdsafdasfdsafdsafdsafds4rttrefds";@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {System.out.println("JwtAuthenticationFilter.doFilterInternal()");// 清除Spring Security上下文中的数据// 避免此前曾经存入过用户信息,后续即使没有携带JWT,在Spring Security仍保存有上下文数据(包括用户信息)System.out.println("清除Spring Security上下文中的数据");SecurityContextHolder.clearContext();// 客户端提交请求时,必须在请求头的Authorization中添加JWT数据,这是当前服务器程序的规定,客户端必须遵守// 尝试获取JWT数据String jwt = request.getHeader("Authorization");System.out.println("从请求头中获取到的JWT=" + jwt);// 判断是否不存在jwt数据// StringUtils.hasText 判断是否属于jwt数据if (!StringUtils.hasText(jwt)) {// 不存在jwt数据,则放行,后续还有其它过滤器及相关组件进行其它的处理,例如未登录则要求登录等// 此处不宜直接阻止运行,因为“登录”、“注册”等请求本应该没有jwt数据System.out.println("请求头中无JWT数据,当前过滤器将放行");filterChain.doFilter(request, response); // 继续执行过滤器链中后续的过滤器return; // 必须}// 注意:此时执行时,如果请求头中携带了Authentication,日志中将输出,且不会有任何响应,因为当前过滤器尚未放行// 以下代码有可能抛出异常的// 密钥和各个Key应该统一定义String username = null;String permissionsString = null;try {System.out.println("请求头中包含JWT,准备解析此数据……");Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();username = claims.get("username").toString();permissionsString = claims.get("permissions").toString();System.out.println("username=" + username);System.out.println("permissionsString=" + permissionsString);} catch (ExpiredJwtException e) {System.out.println("解析JWT失败,此JWT已过期:" + e.getMessage());JsonResult<Void> jsonResult = JsonResult.fail(State.ERR_JWT_EXPIRED, "您的登录已过期,请重新登录!");String jsonString = JSON.toJSONString(jsonResult);System.out.println("响应结果:" + jsonString);response.setContentType("application/json; charset=utf-8");response.getWriter().println(jsonString);return;} catch (MalformedJwtException e) {System.out.println("解析JWT失败,此JWT数据错误,无法解析:" + e.getMessage());JsonResult<Void> jsonResult = JsonResult.fail(State.ERR_JWT_MALFORMED, "获取登录信息失败,请重新登录!");String jsonString = JSON.toJSONString(jsonResult);System.out.println("响应结果:" + jsonString);response.setContentType("application/json; charset=utf-8");response.getWriter().println(jsonString);return;} catch (SignatureException e) {System.out.println("解析JWT失败,此JWT签名错误:" + e.getMessage());JsonResult<Void> jsonResult = JsonResult.fail(State.ERR_JWT_SIGNATURE, "获取登录信息失败,请重新登录!");String jsonString = JSON.toJSONString(jsonResult);System.out.println("响应结果:" + jsonString);response.setContentType("application/json; charset=utf-8");response.getWriter().println(jsonString);return;} catch (Throwable e) {System.out.println("解析JWT失败,异常类型:" + e.getClass().getName());e.printStackTrace();JsonResult<Void> jsonResult = JsonResult.fail(State.ERR_INTERNAL_SERVER_ERROR, "获取登录信息失败,请重新登录!");String jsonString = JSON.toJSONString(jsonResult);System.out.println("响应结果:" + jsonString);response.setContentType("application/json; charset=utf-8");response.getWriter().println(jsonString);return;}// 将此前从JWT中读取到的permissionsString(JSON字符串)转换成Collection<? extends GrantedAuthority>List<SimpleGrantedAuthority> permissions= JSON.parseArray(permissionsString, SimpleGrantedAuthority.class);System.out.println("从JWT中获取到的权限转换成Spring Security要求的类型:" + permissions);// 将解析得到的用户信息传递给Spring Security// 获取Spring Security的上下文,并将Authentication放到上下文中// 在Authentication中封装:用户名、null(密码)、权限列表// 因为接下来并不会处理认证,所以Authentication中不需要密码// 后续,Spring Security发现上下文中有Authentication时,就会视为已登录,甚至可以获取相关信息Authentication authentication= new UsernamePasswordAuthenticationToken(username, null, permissions);SecurityContextHolder.getContext().setAuthentication(authentication);System.out.println("将解析得到的用户信息传递给Spring Security");// 放行System.out.println("JwtAuthenticationFilter 放行");filterChain.doFilter(request, response);}}
要使用Spring Security实现授权访问,首先,必须保证用户登录后,在Spring Security上下文中存在权限相关信息(目前,此项已完成,在JwtAuthenticationFilter
的最后,已经存入权限信息)。
然后,需要在配置类上使用@EnableGlobalMethodSecurity
注解开启“通过注解配置权限”的功能,所以,在SecrutiyConfiguration
类上添加:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 新增
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {// ===== 类中原有代码 =====
}
最后,在任何你需要设置权限的处理请求的方法上,通过@PreAuthorize
注解来配置要求某种权限,例如:
@GetMapping("/hello")
@PreAuthorize("hasAuthority('/ams/admin/read')") // 新增
public String sayHello() {return "hello~~~";
}
完成后,重启项目,使用具有/ams/admin/read
权限的用户可以直接访问,不具有此权限的用户则不能访问
Spring Security框架相关推荐
- spring security框架中在页面通过标签获取用户信息
2019独角兽企业重金招聘Python工程师标准>>> spring security框架中,通过<@sec.authentication property="nam ...
- 关于Spring Security框架 关于单点登录sso
1.Spring Security的作用 Spring Security主要解决了认证和授权相关的问题. 认证(Authenticate):验证用户身份,即登录. 授权(Authorize):允许用户 ...
- Spring Security 框架详解
SECURITY Spring Security框架 Spring Security框架主要解决了认证与授权的相关问题. 添加依赖 在Spring Boot项目中,需要使用Spring Securit ...
- 【Spring Security】Spring Security框架详解
文章目录 前言 一.框架概述 Spring Security的架构 Spring Security的主要特点 二.认证 HTTP Basic认证 表单登录 OpenID Connect 三.授权 基于 ...
- SpringSecurity权限管理框架系列(六)-Spring Security框架自定义配置类详解(二)之authorizeRequests配置详解
1.预置演示环境 这个演示环境继续沿用 SpringSecurit权限管理框架系列(五)-Spring Security框架自定义配置类详解(一)之formLogin配置详解的环境. 2.自定义配置类 ...
- 盘点 Spring Security 框架中的八大经典设计模式
上次有小伙伴建议,源码分析太枯燥了,要是能够结合设计模式一起来,这样更有助于大家理解 Spring Security 源码,同时还能复习一波设计模式. 因此松哥今天就试着整一篇,和大家来聊一聊 Spr ...
- 翼支付门户架构之Spring Security框架介绍
Spring Security3,其前身是"spring的acegi安全系统". 先来谈一谈Acegi的基础知识,Acegi的架构比较复杂.如果对Web资源进行保护,最好的办法莫过 ...
- Spring Security 框架学习之十二 单点登录原理(部分注解是我原创,其余是转载网上电子书内容,献丑了,写的不对请大家见谅,如有侵权我立即删除)(主要是为了自己学的知识备忘)
下图中的redis通常作为抽离出来的独立的外部session对象数据容器 (上面说的是:如果将共享顶级域名下的各个子系统的sessionId(会话标识信息),session对象数据信息,用户身份认 ...
- Java认证授权框架Spring Security介绍
Spring Security 是一个非常强大的身份验证和授权控制框架.为了满足企业项目的不同需求,它提供了很多定制化开发的解决方案,通过简单的调整配置,就能为我们的应用提供一套可靠的安全保障.本节课 ...
最新文章
- swiftsuspenders Memo
- JAVA连接 mongodb(mac OSX)
- Python可视化中的Matplotlib绘图(1.画图,网格,子图,画正余弦图,坐标轴界限,画圆,)
- 第一次使用Linux服务器所栽之坑
- C#中开发之Socket网络编程TCP/IP层次模型、端口及报文等探讨
- mysql 存树 闭包表_关系型数据库树形关系存储-闭包表
- WDS部署服务所用的PXE引导文件
- 微信公众号发红包 php,php微信公众号开发之现金红包
- 函数信号发生器的设计与实现_北邮大二上电子电路基础实验报告
- 机器学习小白入门--统计学知识 Z-Value for Proportions
- 深度学习李宏毅21春_16_GAN_P3
- 教你如何认汽车标志(转)
- 内存频率有哪些?怎么看内存频率
- Pandas GroupBy 深度总结
- electron---windows客户端开发探索
- Oracle增删改查
- perl q qq qr qw qx 区别与使用方法
- 12306登录验证码识别(Java版)
- dreamweaver网页设计作业制作 (NBA篮球网页设计与制作) HTML+CSS
- R语言实战笔记 基本统计分析-相关
热门文章
- PythonWeb_Django_04_bj
- Beyond Compare“授权密钥已被吊销”的解决办法
- 20+ 创意Flash网站设计欣赏
- win10 和Ubuntu双系统安装
- Android 使用Zxing 扫描二维码 和生成二维码
- Android表情文字EmotionText解析
- user script
- java自定义统计报表_用Java报表工具FineReport制作人员统计报表
- 约翰·冯·诺依曼的一生
- RAC环境中openssh版本对SSH互信创建的影响