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属性中。

按照以上规范,则服务器端在每次接收到请求后,首先,就应该判断请求头中是否存在AuthorizationAuthorization的值是否有效等操作,通常,是通过过滤器来实现以上检查的。

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类型
  • 为避免存入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框架相关推荐

  1. spring security框架中在页面通过标签获取用户信息

    2019独角兽企业重金招聘Python工程师标准>>> spring security框架中,通过<@sec.authentication property="nam ...

  2. 关于Spring Security框架 关于单点登录sso

    1.Spring Security的作用 Spring Security主要解决了认证和授权相关的问题. 认证(Authenticate):验证用户身份,即登录. 授权(Authorize):允许用户 ...

  3. Spring Security 框架详解

    SECURITY Spring Security框架 Spring Security框架主要解决了认证与授权的相关问题. 添加依赖 在Spring Boot项目中,需要使用Spring Securit ...

  4. 【Spring Security】Spring Security框架详解

    文章目录 前言 一.框架概述 Spring Security的架构 Spring Security的主要特点 二.认证 HTTP Basic认证 表单登录 OpenID Connect 三.授权 基于 ...

  5. SpringSecurity权限管理框架系列(六)-Spring Security框架自定义配置类详解(二)之authorizeRequests配置详解

    1.预置演示环境 这个演示环境继续沿用 SpringSecurit权限管理框架系列(五)-Spring Security框架自定义配置类详解(一)之formLogin配置详解的环境. 2.自定义配置类 ...

  6. 盘点 Spring Security 框架中的八大经典设计模式

    上次有小伙伴建议,源码分析太枯燥了,要是能够结合设计模式一起来,这样更有助于大家理解 Spring Security 源码,同时还能复习一波设计模式. 因此松哥今天就试着整一篇,和大家来聊一聊 Spr ...

  7. 翼支付门户架构之Spring Security框架介绍

    Spring Security3,其前身是"spring的acegi安全系统". 先来谈一谈Acegi的基础知识,Acegi的架构比较复杂.如果对Web资源进行保护,最好的办法莫过 ...

  8. Spring Security 框架学习之十二 单点登录原理(部分注解是我原创,其余是转载网上电子书内容,献丑了,写的不对请大家见谅,如有侵权我立即删除)(主要是为了自己学的知识备忘)

      下图中的redis通常作为抽离出来的独立的外部session对象数据容器 (上面说的是:如果将共享顶级域名下的各个子系统的sessionId(会话标识信息),session对象数据信息,用户身份认 ...

  9. Java认证授权框架Spring Security介绍

    Spring Security 是一个非常强大的身份验证和授权控制框架.为了满足企业项目的不同需求,它提供了很多定制化开发的解决方案,通过简单的调整配置,就能为我们的应用提供一套可靠的安全保障.本节课 ...

最新文章

  1. swiftsuspenders Memo
  2. JAVA连接 mongodb(mac OSX)
  3. Python可视化中的Matplotlib绘图(1.画图,网格,子图,画正余弦图,坐标轴界限,画圆,)
  4. 第一次使用Linux服务器所栽之坑
  5. C#中开发之Socket网络编程TCP/IP层次模型、端口及报文等探讨
  6. mysql 存树 闭包表_关系型数据库树形关系存储-闭包表
  7. WDS部署服务所用的PXE引导文件
  8. 微信公众号发红包 php,php微信公众号开发之现金红包
  9. 函数信号发生器的设计与实现_北邮大二上电子电路基础实验报告
  10. 机器学习小白入门--统计学知识 Z-Value for Proportions
  11. 深度学习李宏毅21春_16_GAN_P3
  12. 教你如何认汽车标志(转)
  13. 内存频率有哪些?怎么看内存频率
  14. Pandas GroupBy 深度总结
  15. electron---windows客户端开发探索
  16. Oracle增删改查
  17. perl q qq qr qw qx 区别与使用方法
  18. 12306登录验证码识别(Java版)
  19. dreamweaver网页设计作业制作 (NBA篮球网页设计与制作) HTML+CSS
  20. R语言实战笔记 基本统计分析-相关

热门文章

  1. PythonWeb_Django_04_bj
  2. Beyond Compare“授权密钥已被吊销”的解决办法
  3. 20+ 创意Flash网站设计欣赏
  4. win10 和Ubuntu双系统安装
  5. Android 使用Zxing 扫描二维码 和生成二维码
  6. Android表情文字EmotionText解析
  7. user script
  8. java自定义统计报表_用Java报表工具FineReport制作人员统计报表
  9. 约翰·冯·诺依曼的一生
  10. RAC环境中openssh版本对SSH互信创建的影响