学习Spring Boot:(二十八)Spring Security 权限认证

前言

主要实现 Spring Security 的安全认证,结合 RESTful API 的风格,使用无状态的环境。

主要实现是通过请求的 URL ,通过过滤器来做不同的授权策略操作,为该请求提供某个认证的方法,然后进行认证,授权成功返回授权实例信息,供服务调用。

基于Token的身份验证的过程如下:

  1. 用户通过用户名和密码发送请求。
  2. 程序验证。
  3. 程序返回一个签名的token 给客户端。
  4. 客户端储存token,并且每次用于每次发送请求。
  5. 服务端验证token并返回数据。

每一次请求都需要token,所以每次请求都会去验证用户身份,所以这里必须要使用缓存,

基本使用

加入相关依赖

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

了解基础配置

认证的基本信息
public interface UserDetails extends Serializable {//返回分配给用户的角色列表Collection<? extends GrantedAuthority> getAuthorities();//返回密码String getPassword();//返回帐号String getUsername();// 账户是否未过期boolean isAccountNonExpired();// 账户是否未锁定boolean isAccountNonLocked();// 密码是否未过期boolean isCredentialsNonExpired();// 账户是否激活boolean isEnabled();
}
获取基本信息
// 根据用户名查找用户的信息
public interface UserDetailsService {UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

我们只要实现这个扩展,就能够自定义方式获取认证的基本信息

WebSecurityConfigurerAdapter

WebSecurityConfigurerAdapter 提供了一种便利的方式去创建 WebSecurityConfigurer的实例,只需要重写 WebSecurityConfigurerAdapter 的方法,即可配置拦截什么URL、设置什么权限等安全控制。

下面是主要会是要到的几个配置:

    /*** 主要是对身份认证的设置* @param auth* @throws Exception*/  @Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {this.disableLocalConfigureAuthenticationBldr = true;}/*** 复写这个方法来配置 {@link HttpSecurity}. * 通常,子类不能通过调用 super 来调用此方法,因为它可能会覆盖其配置。 默认配置为:* */protected void configure(HttpSecurity http) throws Exception {logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();}/*** Override this method to configure {@link WebSecurity}. For example, if you wish to* ignore certain requests.* 主要是对某些 web 静态资源的设置*/public void configure(WebSecurity web) throws Exception {}

认证流程

阅读源码了解。

Spring Security.jpg

AbstractAuthenticationProcessingFilter.doFilter

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;// 判断是否是需要验证方法(是否是登陆的请求),不是的话直接放过if (!requiresAuthentication(request, response)) {chain.doFilter(request, response);return;}// 登陆的请求开始进行验证Authentication authResult;try {// 开始认证,attemptAuthentication在 UsernamePasswordAuthenticationFilter 中实现authResult = attemptAuthentication(request, response);// return null 认证失败if (authResult == null) {return;}// 篇幅问题,中间很多代码删了successfulAuthentication(request, response, chain, authResult);}

UsernamePasswordAuthenticationFilter.attemptAuthentication

// 接收并解析用户登陆信息,为已验证的用户返回一个已填充的身份验证令牌,表示成功的身份验证,
// 如果身份验证过程失败,就抛出一个AuthenticationException
public Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response) throws AuthenticationException {if (postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}// 方法将 request 中的 username 和 password 生成 UsernamePasswordAuthenticationToken 对象,用于 AuthenticationManager 的验证String username = obtainUsername(request);String password = obtainPassword(request);if (username == null) {username = "";}if (password == null) {password = "";}username = username.trim();UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// Allow subclasses to set the "details" propertysetDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}

ProviderManager.authenticate

验证 Authentication 对象(里面包含着验证对象)

  1. 如果有多个 AuthenticationProvider 支持验证传递过来的Authentication 对象,那么由第一个来确定结果,覆盖早期支持AuthenticationProviders 所引发的任何可能的AuthenticationException。 成功验证后,将不会尝试后续的AuthenticationProvider。
  2. 如果最后所有的 AuthenticationProviders 都没有成功验证 Authentication 对象,将抛出 AuthenticationException。

最后它调用的是 Authentication result = provider.authenticate(authentication);

只要我们自定义 AuthenticationProvider 就能完成自定义认证。

动手实现安全框架

使用的依赖

    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.6</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.7.0</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.7.0</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.7.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency></dependencies>

数据表关系

img

User
@Data
@Entity
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(unique = true, nullable = false, length = 50)private String username;@Column(nullable = false)private String password;@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date createDate;@OneToMany(targetEntity = UserRole.class, mappedBy = "userId", fetch = FetchType.EAGER) // mappedBy 只有在双向关联的时候设置,表示关系维护的一端,否则会生成中间表A_B@org.hibernate.annotations.ForeignKey(name = "none") // 注意这里不能使用 @JoinColumn 不然会生成外键private Set<UserRole> userRoles;
}
Role
@Entity
@Data
public class Role {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(nullable = false, unique = true)private String name;
}
UserRole
@Entity
@Data
public class UserRole {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(length = 50, nullable = false)private Long userId;@ManyToOne(targetEntity = Role.class)@JoinColumn(name = "roleId", nullable = false, foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT))private Role role;
}

流程实现

认证流程:
img

JWT

我使用的是服务端无状态的token 交换的形式,所以引用的是 jwt,首先实现 jwt:

# jwt 配置
jwt:# 加密密钥secret: 61D73234C4F93E03074D74D74D1E39D9 #blog.wuwii.com# token有效时长expire: 7 # 7天,单位天# token 存在 header 中的参数header: token@ConfigurationProperties(prefix = "jwt")
@Data
public class JwtUtil {/*** 密钥*/private String secret;/*** 有效期限*/private int expire;/*** 存储 token*/private String header;/*** 生成jwt token** @param username* @return token*/public String generateToken(String username) {Date nowDate = new Date();return Jwts.builder().setHeaderParam("typ", "JWT")// 后续获取 subject 是 username.setSubject(username).setIssuedAt(nowDate).setExpiration(DateUtils.addDays(nowDate, expire))// 这里我采用的是 HS512 算法.signWith(SignatureAlgorithm.HS512, secret).compact();}/*** 解析 token,* 利用 jjwt 提供的parser传入秘钥,** @param token token* @return 数据声明 Map<String, Object>*/private Claims getClaimByToken(String token) {try {return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();} catch (Exception e) {return null;}}/*** token是否过期** @return true:过期*/public boolean isTokenExpired(Date expiration) {return expiration.before(new Date());}public String getUsernameFromToken(String token) {if (StringUtils.isBlank(token)) {throw new KCException("无效 token", HttpStatus.UNAUTHORIZED.value());}Claims claims = getClaimByToken(token);if (claims == null || isTokenExpired(claims.getExpiration())) {throw new KCException(header + "失效,请重新登录", HttpStatus.UNAUTHORIZED.value());}return claims.getSubject();}
}
实现 UserDetails 和 UserDetailsService

img

实现 UserDetails
public class UserDetailsImpl implements UserDetails {private User user;public UserDetailsImpl(User user) {this.user = user;}/*** 获取权限信息* @return*/@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {Set<UserRole> userRoles = user.getUserRoles();List<GrantedAuthority> auths = new ArrayList<>(userRoles.size());userRoles.parallelStream().forEach(userRole -> {// 默认ROLE_  为前缀,可以更改auths.add(new SimpleGrantedAuthority("ROLE_" + userRole.getRole().getName()));});return auths;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}// 账户是否未过期@JsonIgnore@Overridepublic boolean isAccountNonExpired() {return true;}// 账户是否未锁定@JsonIgnore@Overridepublic boolean isAccountNonLocked() {return true;}// 密码是否未过期@JsonIgnore@Overridepublic boolean isCredentialsNonExpired() {return true;}// 账户是否激活@JsonIgnore@Overridepublic boolean isEnabled() {return true;}
}
实现 UserDetailsService
@Slf4j
@CacheConfig(cacheNames = "users")
public class UserDetailServiceImpl implements UserDetailsService {@Autowiredprivate UserDao userDao;@Override@Cacheablepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userDao.findByUsername(username);if (user == null) {throw new UsernameNotFoundException("Username is not valid.");}log.debug("The User is {}", user);return SecurityModelFactory.create(user);}
}
SecurityModelFactory

转换 UserDetails 的工厂类

public class SecurityModelFactory {public static UserDetails create(User user) {return new UserDetailsImpl(user);}
}
授权认证
登陆过滤器
public class LoginFilter extends UsernamePasswordAuthenticationFilter {@Autowiredprivate JwtUtil jwtUtil;/*** 过滤,我目前使用的是默认的,可以自己看源码按需求更改*/@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// todo 在这里可以按需求进行过滤,根据源码来修改扩展非常方便super.doFilter(request, response, chain);}/*** 如果需要进行登陆认证,会在这里进行预处理*/@Overridepublic Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response) throws AuthenticationException {// todo 在登陆认证的时候,可以做些其他的验证操作,比如验证码return super.attemptAuthentication(request, response);}/*** 登陆成功调用,返回 token*/@Overrideprotected void successfulAuthentication(HttpServletRequest request,HttpServletResponse response,FilterChain chain, Authentication authResult) throws IOException {String token = jwtUtil.generateToken(authResult.getName());response.setStatus(HttpStatus.OK.value());response.getWriter().print(token);}
}
  1. 首先会进入 doFilter 方法中,这里可以自定义定义过滤;
  2. 然后如果是登陆的请求,会进入 attemptAuthentication 组装登陆信息,并且进行登陆认证;
  3. 如果登陆成功,会调用 successfulAuthentication方法。
登陆验证
@Slf4j
public class CustomAuthenticationProvider implements AuthenticationProvider {@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate PasswordEncoder passwordEncoder;/*** 验证登录信息,若登陆成功,设置 Authentication*/@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {String username = authentication.getName();String password = (String) authentication.getCredentials();UserDetails user = userDetailsService.loadUserByUsername(username);if (passwordEncoder.matches(password, user.getPassword())) {Collection<? extends GrantedAuthority> authorities = user.getAuthorities();return new UsernamePasswordAuthenticationToken(username, password, authorities);}throw new BadCredentialsException("The password is not correct.");}/*** 当前 Provider 是否支持对该类型的凭证提供认证服务*/@Overridepublic boolean supports(Class<?> authentication) {return UsernamePasswordAuthenticationToken.class.equals(authentication);}
}

我们自己定义的 AuthenticationProvider 主要是实现前面经过过滤器封装的认证对象 UsernamePasswordAuthenticationToken 进行解析认证,

如果认证成功 就给改 UsernamePasswordAuthenticationToken 设置对应的权限,然后返回 Authentication

  1. 获得认证的信息;
  2. 去数据库查询信息,获取密码解密验证认证信息;
  3. 认证成功,设置权限信息,返回 Authentication,失败抛出异常。
JWT 拦截器
/*** token 校验* BasicAuthenticationFilter 滤器负责处理任何具有HTTP请求头的请求的请求,* 以及一个基本的身份验证方案和一个base64编码的用户名:密码令牌。*/
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {@Autowiredprivate JwtUtil jwtUtil;@Autowiredprivate UserDetailsService userDetailsService;public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {super(authenticationManager);}/*** 在此方法中检验客户端请求头中的token,* 如果存在并合法,就把token中的信息封装到 Authentication 类型的对象中,* 最后使用  SecurityContextHolder.getContext().setAuthentication(authentication); 改变或删除当前已经验证的 pricipal** @param request* @param response* @param chain* @throws IOException* @throws ServletException*/@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {String token = request.getHeader(jwtUtil.getHeader());//判断是否有tokenif (token == null) {chain.doFilter(request, response);return;}// 通过token 获取账户信息,并且存入到将身份信息存放在安全系统的上下文。UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(token);SecurityContextHolder.getContext().setAuthentication(authenticationToken);//放行chain.doFilter(request, response);}/*** 解析token中的信息*/private UsernamePasswordAuthenticationToken getAuthentication(String token) {String username = jwtUtil.getUsernameFromToken(token);UserDetails userDetails = userDetailsService.loadUserByUsername(username);if (username != null) {return new UsernamePasswordAuthenticationToken(username, null, userDetails.getAuthorities());}return null;}
}
  1. 请求进入 doFilterInternal 方法中,对请求是否带token进行判断,
  2. 如果没有token,则直接放行请求;
  3. 如果有 token,则解析它的 post;
配置权限和相关设置

自定义配置 Spring Security 配置类 WebSecurityConfig,进项相关配置,并且将所需要的类注入到系统中。

@Configuration
@EnableWebSecurity // 开启 Security
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
//jsr250Enabled有三种注解,分别是@RolesAllowed,@PermitAll,@DenyAll,功能跟名字一样,
// securedEnabled 开启注解
// prePostEnabled  类似用的最多的是 @PreAuthorize
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic JwtUtil jwtUtil() {return new JwtUtil();}/*** 注入 LoginFilter 时候需要,注入 authenticationManager*/@Beanpublic LoginFilter loginFilter() throws Exception {LoginFilter loginFilter = new LoginFilter();loginFilter.setAuthenticationManager(authenticationManager());return loginFilter;}@Beanpublic JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {return new JwtAuthenticationFilter(authenticationManager());}@Beanpublic UserDetailsService customService() {return new UserDetailServiceImpl();}/*** 认证 AuthenticationProvider*/@Beanpublic AuthenticationProvider authenticationProvider() {return new CustomAuthenticationProvider();}/*** BCrypt算法免除存储salt* BCrypt算法将salt随机并混入最终加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理salt问题。* @return*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder(5);}/*** 主要是对身份验证的设置*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth// 注入身份的 Bean.authenticationProvider(authenticationProvider()).userDetailsService(userDetailsService())// 默认登陆的加密,自定义登陆的时候无效.passwordEncoder(passwordEncoder());// 在内存中设置固定的账户密码以及身份信息/*auth.inMemoryAuthentication().withUser("user").password("password").roles("USER").and().withUser("admin").password("password").roles("USER", "ADMIN");*/}/**** @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http// 关闭 csrf.csrf().disable()// 设置 session 状态 STATELESS 无状态.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 需要权限验证.mvcMatchers("/user/**").authenticated().and()// 登陆页面.formLogin()//.loginPage("/login.html")// 登陆成功跳转页面.defaultSuccessUrl("/")//.failureForwardUrl("/login.html").permitAll().and()// 登出//.logout()// 注销的时候删除会话//.deleteCookies("JSESSIONID")// 默认登出请求为 /logout,可以用下面自定义//.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))// 自定义登出成功的页面,默认为登陆页//.logoutSuccessUrl("/logout.html")//.permitAll()//.and()// 开启 cookie 保存用户信息//.rememberMe()// cookie 有效时间//.tokenValiditySeconds(60 * 60 * 24 * 7)// 设置cookie 的私钥,默认为随机生成的key//.key("remember")//.and()//验证登陆的 filter.addFilter(loginFilter())//验证token的 filter.addFilter(jwtAuthenticationFilter());}/*** Web层面的配置,一般用来配置无需安全检查的路径* @param web* @throws Exception*/@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("**.js","**.css","/images/**","/webjars/**","/**/favicon.ico");}
}
权限控制
@RestController
@RequestMapping(value = "/user", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@PreAuthorize("hasRole('USER')")
public class UserController {@Autowiredprivate UserService userService;@GetMapping@PreAuthorize("hasRole('admin')")public ResponseEntity<List<UserVO>> getAllUser() {List<User> users = userService.findAll();List<UserVO> userViews = userService.castUserVO(users);return ResponseEntity.ok(userViews);}
}

请求上面的getAllUser 方法,需要当前用户同时拥有 ROLE_USERROLE_admin 两个权限,才能通过权限验证。

在 @PreAuthorize 中我们可以利用内建的 SPEL 表达式:比如 ‘hasRole()’ 来决定哪些用户有权访问。需注意的一点是 hasRole 表达式认为每个角色名字前都有一个前缀 ‘ROLE_’。

迭代上个版本

后来,我发现进行用户认证的时候,会将所有的 provider 都尝试一遍,那么外面将登陆的 UsernameAndPasswordTokenJwtTToken 都可以分别进行验证进行了啊,所有我预先定义 UsernamePasswordAuthenticationToken 包装登陆的信息,然后进入登陆的 AuthenticationProvider 进行认证,token 验证形式,使用 PreAuthenticatedAuthenticationToken 的包装,然后进入例外一个 AuthenticationProvider 中认证。

现在我们的流程就更加清晰了。

img

所以现在我对以前的权限配置以及认证进行了一些更改:

过滤器

在这里,我根据不同请求的类型,进行不同的适配,然后进行加工分装成不同的认证凭证,然后根据凭证的不同,进行不同的认证。

@Slf4j
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {@Autowiredprivate JwtUtil jwtUtil;@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletRequest httpRequest = (HttpServletRequest) request;HttpServletResponse httpResponse = (HttpServletResponse) response;try {if (isLoginRequest(httpRequest, httpResponse)) {Authentication authResult = processLogin(httpRequest, httpResponse);successfulAuthentication(httpRequest, httpResponse, chain, authResult);return;}String token = obtainToken(httpRequest);if (StringUtils.isNotBlank(token)) {processTokenAuthentication(token);}} catch (AuthenticationException e) {unsuccessfulAuthentication(httpRequest, httpResponse, e);return;}chain.doFilter(request, response);}/*** 登陆成功调用,返回 token*/@Overrideprotected void successfulAuthentication(HttpServletRequest request,HttpServletResponse response,FilterChain chain, Authentication authResult) throws IOException {String token = jwtUtil.generateToken(authResult.getName());response.setStatus(HttpStatus.OK.value());response.getWriter().print(token);}private boolean isLoginRequest(HttpServletRequest request, HttpServletResponse response) {return requiresAuthentication(request, response) && "POST".equalsIgnoreCase(request.getMethod());}private String obtainToken(HttpServletRequest request) {return request.getHeader(jwtUtil.getHeader());}private Authentication processLogin(HttpServletRequest request, HttpServletResponse response) {String username = obtainUsername(request);String password = obtainPassword(request);return tryAuthenticationWithUsernameAndPassword(username, password);}private void processTokenAuthentication(String token) {Authentication resultOfAuthentication = tryToAuthenticateWithToken(token);// 设置上下文用户信息以及权限SecurityContextHolder.getContext().setAuthentication(resultOfAuthentication);}private Authentication tryAuthenticationWithUsernameAndPassword(String username, String password) {Authentication authentication = new UsernamePasswordAuthenticationToken(username, password);return tryToAuthenticate(authentication);}private Authentication tryToAuthenticateWithToken(String token) {PreAuthenticatedAuthenticationToken requestAuthentication = new PreAuthenticatedAuthenticationToken(token, null);return tryToAuthenticate(requestAuthentication);}private Authentication tryToAuthenticate(Authentication requestAuth) {Authentication responseAuth = getAuthenticationManager().authenticate(requestAuth);if (responseAuth == null || !responseAuth.isAuthenticated()) {throw new InternalAuthenticationServiceException("Unable to authenticate User for provided credentials");}log.debug("User successfully authenticated");return responseAuth;}
}

授权认证

根据提供的凭证的类型,进行相关的验证操作

LoginAuthenticationProvider

跟上个版本的 登陆验证中的 CustomAuthenticationProvider 代码一样实现一样。

TokenAuthenticateProvider

根据 token 查找它的 权限 信息,并装在到认证的凭证中。

public class TokenAuthenticateProvider implements AuthenticationProvider {@Autowiredprivate JwtUtil jwtUtil;@Autowiredprivate UserDetailsService userDetailsService;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {String token = authentication.getName();String username = jwtUtil.getUsernameFromToken(token);UserDetails userDetails = userDetailsService.loadUserByUsername(username);return new PreAuthenticatedAuthenticationToken(username, null, userDetails.getAuthorities());}@Overridepublic boolean supports(Class<?> authentication) {return PreAuthenticatedAuthenticationToken.class.equals(authentication);}
}

配置权限和相关设置

和上个版本没什么变化,只是将类换了一下

@Configuration
@EnableWebSecurity // 开启 Security
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic JwtUtil jwtUtil() {return new JwtUtil();}@Beanpublic UserDetailsService customService() {return new UserDetailServiceImpl();}@Bean("loginAuthenticationProvider")public AuthenticationProvider loginAuthenticationProvider() {return new LoginAuthenticationProvider();}@Bean("tokenAuthenticationProvider")public AuthenticationProvider tokenAuthenticationProvider() {return new TokenAuthenticateProvider();}@Beanpublic AuthenticationFilter authenticationFilter() throws Exception {AuthenticationFilter authenticationFilter = new AuthenticationFilter();authenticationFilter.setAuthenticationManager(authenticationManager());return authenticationFilter;}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder(5);}@Bean@Overridepublic UserDetailsService userDetailsService() {return new UserDetailServiceImpl();}/*** 主要是对身份验证的设置*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.authenticationProvider(loginAuthenticationProvider()).authenticationProvider(tokenAuthenticationProvider()).userDetailsService(userDetailsService());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http// 关闭 csrf.csrf().disable()// 设置 session 状态 STATELESS 无状态.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 需要权限验证.mvcMatchers("/user/**").authenticated().and()// 登陆页面.formLogin()//.loginPage("/login.html")// 登陆成功跳转页面.defaultSuccessUrl("/").failureForwardUrl("/login.html").permitAll().and().addFilter(authenticationFilter());}
}

后续完善

  1. 修改密码,登出操作 token 的失效机制;
  2. OAuth2 授权服务器的搭建;
  3. 修改权限后,下次请求刷新权限;
  4. ……

附录一:HttpSecurity常用方法

方法说明
openidLogin()用于基于 OpenId 的验证
headers()将安全标头添加到响应
cors()配置跨域资源共享( CORS )
sessionManagement()允许配置会话管理
portMapper()允许配置一个PortMapper(HttpSecurity#(getSharedObject(class))),其他提供SecurityConfigurer的对象使用 PortMapper 从 HTTP 重定向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443
jee()配置基于容器的预认证。 在这种情况下,认证由Servlet容器管理
x509()配置基于x509的认证
rememberMe允许配置“记住我”的验证
authorizeRequests()允许基于使用HttpServletRequest限制访问
requestCache()允许配置请求缓存
exceptionHandling()允许配置错误处理
securityContext()HttpServletRequests之间的SecurityContextHolder上设置SecurityContext的管理。 当使用WebSecurityConfigurerAdapter时,这将自动应用
servletApi()HttpServletRequest方法与在其上找到的值集成到SecurityContext中。 当使用WebSecurityConfigurerAdapter时,这将自动应用
csrf()添加 CSRF 支持,使用WebSecurityConfigurerAdapter时,默认启用
logout()添加退出登录支持。当使用WebSecurityConfigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?success”
anonymous()允许配置匿名用户的表示方法。 当与WebSecurityConfigurerAdapter结合使用时,这将自动应用。 默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS”
formLogin()指定支持基于表单的身份验证。如果未指定FormLoginConfigurer#loginPage(String),则将生成默认登录页面
oauth2Login()根据外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份验证
requiresChannel()配置通道安全。为了使该配置有用,必须提供至少一个到所需信道的映射
httpBasic()配置 Http Basic 验证
addFilterAt()在指定的Filter类的位置添加过滤器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/556798.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 目录所属组设置,Linux系统用户与组管理命令及配置文件总结

一、Linux系统用户及组分类1、用户类别Linux系统中的用户大致可分为三类&#xff1a;root用户、系统用户、普通用户。每一个用户都拥有一个唯一的身份标识UID。2、组分类与用户信息对应的&#xff0c;Linux系统中的组也可分为三类&#xff1a;root组、系统组、普通组。每一个组…

实验楼 linux内核原理与分析,《Linux内核原理与分析》第一周作业 20189210

实验一 Linux系统简介这一节主要学习了Linux的历史&#xff0c;Linux有关的重要人物以及学习Linux的方法&#xff0c;Linux和Windows的区别。其中学到了LInux中的应用程序大都为开源自由的软件&#xff0c;用户可以修改定制再发布&#xff1b;内核是实现多任务运行和硬件管理的…

MySQL的INSERT INTO··· ON DUPLICATE KEY UPDATE使用的几种情况

保存或更新 在MySQL数据库中&#xff0c;如果在insert语句后面带上ON DUPLICATE KEY UPDATE 子句&#xff0c;而要插入的行与表中现有记录的惟一索引或主键中产生重复值&#xff0c;那么就会发生旧行的更新&#xff1b;如果插入的行数据与现有表中记录的唯一索引或者主键不重复…

java8 Stream API详解

文章目录一、Stream流概述二、创建Stream的方式相关API三、Stream的中间操作筛选与切片映射排序四、终止操作第一大类API&#xff08;太过简单&#xff09;第二大类AP归约收集一、Stream流概述 1、java8中有两大最为重要的改变&#xff0c;第一就是Lambda表达式&#xff0c;另…

linux实验3编写内核模块,实验2.3_内核模块_实验报告

实验报告题目: 内核模块实验1、实验目的模块是Linux系统的一种特有机制&#xff0c;可用以动态扩展操作系统内核功能。编写实现某些特定功能的模块&#xff0c;将其作为内核的一部分在管态下运行。本实验通过内核模块编程在/porc文件系统中实现系统时钟的读操作接口。2、实验内…

Java 8 Stream Api 中的 peek、map、foreach区别

#1. 前言 我在Java8 Stream中讲述了 Java 8 Stream API 的一些内容。今天再看一下peek、map、foreach区别。 2. peek peek 操作接收的是一个 Consumer 函数。顾名思义 peek 操作会按照 Consumer 函数提供的逻辑去消费流中的每一个元素&#xff0c;同时有可能改变元素内部的一…

wapper打成linux服务,Wrapper配置详解及高级应用(转)

转自&#xff1a;http://286.iteye.com/blog/1921414将一个简单的程度如HelloWorld 的应用包装秤Wrapper 服务并不复杂&#xff0c;甚至可以认为非常简单。但是实际项目应用过程中我们的程序一般较庞大&#xff0c;运行环境也较复杂。通过Wrapper 配置文件的分析与配置进一步了…

Java8 Stream 流机制和 Lambda 表达式

一、Stream 流介绍与使用场景 Stream 流介绍 java8 中的stream 与InputStream和OutputStream是完全不同的概念, stream 是用于对集合迭代器的增强&#xff0c;使之完成能够完成更高效的聚合操作&#xff08;过滤、排序、统计分组&#xff09;或者大批量数据操作。stream 与 L…

linux guide编译器,GUIDE编译器-GUIDE编程工具-GUIDE编译器下载 v1.0.2官方版-完美下载...

GUIDE编译器是款跨平台的开发环境编程工具&#xff0c;支持C/C 和 Pascal三种语言&#xff0c;具有跨平台、操作简单、跨编程语言和单文件编译调试等特点&#xff0c;为用户提供单文件编译、调试和运行的环境。GUIDE编译器特色1、跨平台&#xff1a; GUIDE 可在 linux 平台和 w…

MySQL事务隔离级别和实现原理

经常提到数据库的事务&#xff0c;那你知道数据库还有事务隔离的说法吗&#xff0c;事务隔离还有隔离级别&#xff0c;那什么是事务隔离&#xff0c;隔离级别又是什么呢&#xff1f;本文就帮大家梳理一下。 MySQL 事务 本文所说的 MySQL 事务都是指在 InnoDB 引擎下&#xff0…

超级全面的MySQL优化面试解析

推荐阅读(点击即可跳转阅读) 1. SpringBoot内容聚合 2. 面试题内容聚合 3. 设计模式内容聚合 4. Mybatis内容聚合 5. 多线程内容聚合 超级全面的MySQL优化面试解析 本文概要 概述 为什么要优化 系统的吞吐量瓶颈往往出现在数据库的访问速度上随着应用程序的运行&#…

mysql linux导入csv主键,MySQL导入csv文件内容到Table及数据库的自增主键设置

写在前面目的是测试将csv文件内容导入到表中, 同时记录一下自增主键的设置.测试采用MySQL8.0.新建表customer_info如下, 未设置主键.修改上表, 添加主键id, 并设置为自增.ALTER TABLE customer_info ADD COLUMN id INT AUTO_INCREMENT NOT NULL PRIMARY KEY;导入步骤1.为了模拟…

mysql 优化方法有哪些?

MySQL索引 MySQL支持诸多存储引擎&#xff0c;而各种存储引擎对索引的支持也各不相同&#xff0c;因此MySQL数据库支持多种索引类型&#xff0c;如BTree索引&#xff0c;哈希索引&#xff0c;全文索引等等。为了避免混乱&#xff0c;本文将只关注于BTree索引&#xff0c;因为这…

Java基础面试题与答案

八种基本数据类型以及包装类 八种基本数据类型默认值&#xff1f;大小&#xff1f;范围区间&#xff1f;包装类的缓存区间&#xff1f; 序号类型名称默认值大小最小值最大值包装类缓冲区间1booleanfalse1B0(false)1(true)Boolean无2byte(byte)01B-128127Byte-128 ~ 1273char‘…

学习vim的linux游戏,PacVim:一个学习 vim 命令的命令行游戏 | Linux 中国

作者 | Sk 译者 | geekpi &#x1f48e; &#x1f48e; 共计翻译&#xff1a;735 篇 贡献时间&#xff1a;1691 天你好&#xff0c;Vim用户&#xff01;今天&#xff0c;我偶然发现了一个很酷的程序来提高 Vim 的使用技巧。Vim 是编写和编辑代码的绝佳编辑器。然而&#x…

Java多线程面试题与答案

线程 线程与进程的区别是什么&#xff1f; 进程指的是应用程序在操作系统中执行的副本&#xff08;系统分配资源的最小单位&#xff09;&#xff0c;线程是程序执行的最小单位&#xff1b;进程使用独立的数据空间&#xff0c;而线程共享进程的数据空间。 线程状态图 多线程会…

JVM面试题与答案

JVM内存布局 JVM在内存布局上可以分为哪些区域&#xff1f; 堆&#xff08;线程共享&#xff09;&#xff1a;GC的主要回收地&#xff0c;包含几乎所有的实例对象、字符串常量池&#xff1b;元空间&#xff08;线程共享&#xff09;&#xff1a;在本地内存分配&#xff0c;包…

md0和md1linux软raid,软RAID管理命令mdadm详解

mdadm是linux下用于创建和管理软件RAID的命令&#xff0c;是一个模式化命令。但由于现在服务器一般都带有RAID阵列卡&#xff0c;并且RAID阵列卡也很廉价&#xff0c;且由于软件RAID的自身缺陷(不能用作启动分区、使用CPU实现&#xff0c;降低CPU利用率)&#xff0c;因此在生产…

Dubbo常见面试题与答案

Dubbo的基础知识 Dubbo的核心架构是怎样的&#xff1f; Registry&#xff1a;注册中心。 负责服务地址的注册与查找&#xff0c;服务的 Provider 和 Consumer 只在启动时与注册中心交互。注册中心通过长连接感知 Provider 的存在&#xff0c;在 Provider 出现宕机的时候&#…

Redis常见面试题与答案

Redis的基本数据类型 Redis有哪些常用的数据类型&#xff1f; String&#xff1a;字符串&#xff08;最常用的缓存&#xff09;Hash&#xff1a;哈希&#xff08;保存对象&#xff09;List&#xff1a;有序列表&#xff08;消息队列&#xff09;Set&#xff1a;无序集合&…