赞
踩
当初始化Spring Security时,会创建一个名为 SpringSecurityFilterChain 的Servlet过滤器,类型为org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此 类,下图是Spring Security过虑器链结构图:
FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时 这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认 证,也不直接处理用户的授权,而是把它们交给了认证管理器(AuthenticationManager)和决策管理器 (AccessDecisionManager)进行处理
package org.springframework.security.crypto.password;
// 源码解读
public interface PasswordEncoder {
// 接收原始密码的字符串,返回一个经过加密后的哈希值,不能逆向解密
String encode(CharSequence var1);
// 用来校验用户输入的密码,和加密后的密码比较,一般用于登录
boolean matches(CharSequence var1, String var2);
// 判断加密后的密码是否需要重新加密,默认是不需要。如果必须要一个月修改一次密码,可以重写这个方法
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
package com.zimug.courses.security.basic.config; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; /** * @author pangjian * @ClassName SpringSecurityConfig * @Description Spring Security配置 * @date 2021/6/3 9:56 */ @Configuration public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { /** * @Description: 这个方法的作用是进行安全认证及授权规则配置 * @Param http: * @return void * @date 2021/6/3 10:03 */ @Override protected void configure(HttpSecurity http) throws Exception { http.httpBasic()// 开启httpbasic认证 .and() .authorizeRequests()//资源控制逻辑 .anyRequest()//所有请求 .authenticated();//请求认证规则 } }
开启后访问系统资源就要求输入账号密码,密码是启动项目时就给你的,账号默认是user
在application.yml中配置
spring:
security:
user:
name: admin
password: admin
找到请求头的Authorization,复制它对应的值,去Base64解密出来即可
<form action="/login" method="post">
<span>用户名称</span><input type="text" name="name" /> <br>
<span>用户密码</span><input type="password" name="psd" /> <br>
<input type="submit" value="登陆">
</form>
package com.zimug.courses.security.basic.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; /** * @author pangjian * @ClassName SpringSecurityConfig * @Description Spring Security配置 * @date 2021/6/3 9:56 */ @Configuration public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { /** * @Description: 这个方法的作用是进行安全认证及授权规则配置 * @Param http: * @return void * @date 2021/6/3 10:03 */ @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable()// 关闭跨站防御 .formLogin() // 开启formLogin认证 //登录认证逻辑 .loginPage("/login.html") // 一旦用户请求没有权限就跳转到这个页面 .loginProcessingUrl("/login")// 登录表单form中的action地址,也就是处理认证请求的路径 .usernameParameter("name")// 接收表单数据 .passwordParameter("psd")// 接收表单数据 .defaultSuccessUrl("/")// 登录认证成功后默认跳转的路径 .and() // 资源控制逻辑 .authorizeRequests() .antMatchers("/login.html","/login") .permitAll()// 允许任何人访问 .antMatchers("/","biz1","biz2") .hasAnyAuthority("ROLE_user","ROLE_admin")//给上面的资源加上权限规则 .antMatchers("/syslog","/sysuser") .hasAnyRole("admin")//给上面资源加上权限规则 .anyRequest() .authenticated();// 所有请求都需要登录认证才能访问 } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() // 静态写死该用户的用户名user和密码123456,还给他固定的角色user,然后该用户只能访问biz1.html和biz2.html .withUser("user") .password(passwordEncoder().encode("123456")) .roles("user") .and() .withUser("admin") .password(passwordEncoder().encode("123456")) .roles("admin") .and() .passwordEncoder(passwordEncoder());// 配置BCrypt加密 } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override public void configure(WebSecurity web) throws Exception { // 将项目中的静态资源路径开发,这里配置是不需要经过Filter过滤器的 web.ignoring().antMatchers("/css/**","/fonts/**","/img/**","/js/**"); } }
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { // 只允许post协议进行认证 if (this.postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } else { // 获取用户名和密码 String username = this.obtainUsername(request); String password = this.obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); // 把用户名和密码构建成一个token UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); this.setDetails(request, authRequest); // 调用方法对这个token进行认证,认证成功返回一个Authentication登录认证的主体 return this.getAuthenticationManager().authenticate(authRequest); } }
UsernamePasswordAuthenticationFilter是Authtication的子类,认证之前是token令牌,认证之后是主体信息
它的父类AbstractAuthenticationProcessingFilter有两个属性
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
登录成功或失败后的处理器,如果我们登录失败了不想跳转其他页面可用实现它的接口方法
public interface AuthenticationFailureHandler {
void onAuthenticationFailure(HttpServletRequest var1, HttpServletResponse var2, AuthenticationException var3) throws IOException, ServletException;
}
package org.springframework.security.authentication;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
public interface AuthenticationManager {
// 只有一个登录认证的方法,要传入一个登录认证主体,也就是前端传过来的信息被封装成了一个Authentication
Authentication authenticate(Authentication var1) throws AuthenticationException;
}
也就是如下Authentication接口,看看它有哪些属性
package org.springframework.security.core; import java.io.Serializable; import java.security.Principal; import java.util.Collection; public interface Authentication extends Principal, Serializable { // 返回一个权限集合 Collection<? extends GrantedAuthority> getAuthorities(); Object getCredentials(); // 用户细节 Object getDetails(); // 用户主体信息 Object getPrincipal(); // 主体是否被认证,如果没有认证是不能访问后端的接口的 boolean isAuthenticated(); void setAuthenticated(boolean var1) throws IllegalArgumentException; }
ProviderManager是AuthenticationManager的实现核心类,用来登录认证的,ProviderManager有这一个属性:
private List<AuthenticationProvider> providers;
DaoAuthenticationProvider是AuthenticationProvider的实现类,用来去数据库加载用户名、密码和权限的类,它有下面这个核心的方法:
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { this.prepareTimingAttackProtection(); try { // 重要,它根据用户名username去加载该用户的信息,用一个UserDetails去接收信息 UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); if (loadedUser == null) { throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation"); } else { return loadedUser; } } catch (UsernameNotFoundException var4) { this.mitigateAgainstTimingAttack(authentication); throw var4; } catch (InternalAuthenticationServiceException var5) { throw var5; } catch (Exception var6) { throw new InternalAuthenticationServiceException(var6.getMessage(), var6); } }
UserDetails接口有什么属性,这个信息要从数据库查询出来
public interface UserDetails extends Serializable { // 登录用户的权限 Collection<? extends GrantedAuthority> getAuthorities(); // 登录用户的密码 String getPassword(); // 用户名 String getUsername(); // 账户是否过期 boolean isAccountNonExpired(); // 账户是否被锁定 boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); // 当前账户是否可用 boolean isEnabled(); }
查询出来的主体信息(Authentication)会保存到SecurityContext,也就是上下文中,它已经经过认证了,下次登录就直接从上下文拿出来,不需要经过那么多过滤了,直接访问Controller
package com.zimug.courses.security.basic.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.zimug.courses.security.basic.Resp.Resp; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author pangjian * @ClassName MyAuthenticationFailureHandler * @Description 自定义验证失败后跳转逻辑,返回和接收的要是json数据 * @date 2021/6/3 12:56 */ @Component public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { @Value("${spring.security.logintype}") private String loginType; // 将对象变成json数据的处理对象 private static ObjectMapper objectMapper = new ObjectMapper(); @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { if(loginType.equalsIgnoreCase("JSON")){ response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString( Resp.fail() )); } else { response.setContentType("application/json;charset=UTF-8"); super.onAuthenticationFailure(request,response,exception); } } }
package com.zimug.courses.security.basic.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.zimug.courses.security.basic.Resp.Resp; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author pangjian * @ClassName MyAuthenticationSuccessHandler * @Description 自定义验证成功后跳转逻辑 * @date 2021/6/3 12:49 */ @Component public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { @Value("${spring.security.logintype}") private String loginType; private static ObjectMapper objectMapper = new ObjectMapper(); @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { if(loginType.equalsIgnoreCase("JSON")){ response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString( Resp.success()) ); } else { // 会帮我们跳转到上次请求的页面上 super.onAuthenticationSuccess(request,response,authentication); } } }
package com.zimug.courses.security.basic.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import javax.annotation.Resource; /** * @author pangjian * @ClassName SpringSecurityConfig * @Description Spring Security配置 * @date 2021/6/3 9:56 */ @Configuration public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Resource private MyAuthenticationFailureHandler myAuthenticationFailureHandler; @Resource private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler; /** * @Description: 这个方法的作用是进行安全认证及授权规则配置 * @Param http: * @return void * @date 2021/6/3 10:03 */ @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable()// 关闭跨站防御 .formLogin() // 开启formLogin认证 //登录认证逻辑 .loginPage("/login.html") // 一旦用户请求没有权限就跳转到这个页面 .loginProcessingUrl("/login")// 登录表单form中的action地址,也就是处理认证请求的路径 .usernameParameter("name")// 接收表单数据 .passwordParameter("psd")// 接收表单数据 // .defaultSuccessUrl("/")// 登录认证成功后默认跳转的路径 .successHandler(myAuthenticationSuccessHandler) .failureHandler(myAuthenticationFailureHandler) } }
这个接口的信息要提供给spring security才能进行认证鉴权
通过UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException方法去数据库加载
package com.zimug.courses.security.basic.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; /** * @author pangjian * @ClassName User * @Description SpringSecurity通过实现的接口方法去获取该实例化的值,我们通过set()方法注入后,在UserDetailsService接口就能返回该User实现类的实例化对象,让Spring Security拿到该自定义用户的信息 * @date 2021/6/3 21:09 */ @Data @NoArgsConstructor @AllArgsConstructor public class User implements UserDetails { private String password; private String username; private boolean enabled; private Collection<? extends GrantedAuthority> authorities; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } // 我们不希望那么麻烦去限制用户的登录,只通过enabled就行,就给其他的返回一个默认值true给他通过 @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
@Override @Bean public UserDetailsService userDetailsService(){ return username-> { Admin admin = adminService.getAdminByUserName(username); if(null!=admin){ List<String> roleList = roleMapper.selectRoleByUsername(admin.getUsername()); List<String> authorities = menuMapper.selectMenuByRole(admin.getUsername()); authorities.addAll(roleList); admin.setAuthorities( AuthorityUtils.commaSeparatedStringToAuthorityList( String.join(",",authorities) ) ); // 该自定义的用户类已经实现了UserDetails接口,刚好符合了该方法的返回类型 return admin; } return null; }; }
package cn.guet.server.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * @author pangjian * @ClassName JwtTokenUtil * @Description Jwt工具类 * @date 2021/6/2 19:37 */ @Component public class JwtTokenUtil { private static final String CLAIM_KEY_USERNAME="sub"; private static final String CLAIM_KEY_CREATED="created"; @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; /** * @Description: 根据已成功登录的用户信息生成token * @Param userDetails:spring security框架中的接口 * @return java.lang.String * @date 2021/6/2 19:46 */ public String generateToken(UserDetails userDetails){ Map<String,Object> claim = new HashMap<>(); claim.put(CLAIM_KEY_USERNAME,userDetails.getUsername()); claim.put(CLAIM_KEY_CREATED,new Date()); return generateToken(claim); } /** * @Description: 验证token,一要认证从 前端传过来的token的里面的用户名 和 账号密码查询出来的用户名 是否一致,二要认证时间是否过期 * @Param token: 待认证的token * @Param userDetails: spring security框架中的类 * @return boolean * @date 2021/6/2 20:04 */ public boolean validateToken(String token,UserDetails userDetails){ String username = getUserNameFromToken(token); return username.equals(userDetails.getUsername()) && !isTokenExpired(token); } /** * @Description: token是否能被刷新,没过期是不可以被刷新,过期了可以被刷新 * @Param token: * @return boolean * @date 2021/6/2 20:17 */ public boolean canRefresh(String token){ return !isTokenExpired(token); } /** * @Description: 刷新token * @Param token: * @return java.lang.String * @date 2021/6/2 20:21 */ public String refreshToken(String token){ Claims claims = getClaimsFromToken(token); claims.put(CLAIM_KEY_CREATED,new Date()); return generateToken(claims); } /** * @Description: 判断token是否失效 * @Param token: * @return boolean * @date 2021/6/2 20:05 */ private boolean isTokenExpired(String token) { Date expireDate = getExpiredDateFromToken(token); // 过期时间要在当前时间之后 return expireDate.before(new Date()); } /** * @Description: 从token中获取过期时间 * @Param token: * @return java.util.Date * @date 2021/6/2 20:09 */ private Date getExpiredDateFromToken(String token) { Claims claims = getClaimsFromToken(token); return claims.getExpiration(); } /** * @Description: 从token中获取登录用户名 * @Param token: token * @return java.lang.String * @date 2021/6/2 19:53 */ public String getUserNameFromToken(String token){ String username; try{ Claims claims = getClaimsFromToken(token); username = claims.getSubject(); } catch (Exception e) { username = null; } return username; } /** * @Description: * @Param token: 从token中获取荷载,需要密钥 * @return io.jsonwebtoken.Claims * @date 2021/6/2 19:57 */ private Claims getClaimsFromToken(String token) { Claims claims = null; try { claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } catch (Exception e){ e.printStackTrace(); } return claims; } /** * @Description: 根据荷载生成token * @Param claim: 荷载 * @return java.lang.String * @date 2021/6/2 19:51 */ private String generateToken(Map<String,Object> claim){ return Jwts.builder() .setClaims(claim) // 设置过期时间:当前时间加失效时间 .setExpiration(generateExpirationDate()) .signWith(SignatureAlgorithm.HS512,secret) .compact(); } /** * @Description: 生成失效时间 * @return java.util.Date * @date 2021/6/2 19:49 */ private Date generateExpirationDate() { return new Date(System.currentTimeMillis()+expiration*1000); } }
这个接口的信息要提供给spring security才能进行认证鉴权
package cn.guet.server.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.io.Serializable; import java.util.Collection; import java.util.List; /** * <p> * 通过实现的接口方法去获取该实例化的值,我们通过set()方法注入后,在UserDetailsService接口就能返回该User实现类的实例化对象,让Spring Security拿到该自定义用户的信息 * </p> * * @author pangjian * @since 2021-06-02 */ @Data @EqualsAndHashCode(callSuper = false) @TableName("t_admin") @ApiModel(value="Admin对象", description="") public class Admin implements Serializable , UserDetails { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "id") @TableId(value = "id", type = IdType.AUTO) private Integer id; @ApiModelProperty(value = "姓名") private String name; @ApiModelProperty(value = "手机号码") private String phone; @ApiModelProperty(value = "住宅电话") private String telephone; @ApiModelProperty(value = "联系地址") private String address; @ApiModelProperty(value = "是否启用") private Boolean enabled; @ApiModelProperty(value = "用户名") private String username; @ApiModelProperty(value = "密码") private String password; @ApiModelProperty(value = "用户头像") private String userFace; @ApiModelProperty(value = "备注") private String remark; @TableField(exist = false) private List<GrantedAuthority> authorities; /** * @Description:用户权限 * @return java.util.Collection<? extends org.springframework.security.core.GrantedAuthority> * @date 2021/8/5 10:46 */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } /** * @Description:用不到的判断字段统一返回true * @return boolean * @date 2021/8/5 10:44 */ @Override public boolean isCredentialsNonExpired() { return true; } /** * @Description:我们用到了账号是否被锁定,返回从数据库查出来的真实数据 * @return boolean * @date 2021/8/5 10:45 */ @Override public boolean isEnabled() { return enabled; } }
自己写一个类继承WebSecurityConfigurerAdapter,然后重写它的三个方法
package cn.guet.server.config; import cn.guet.server.Filter.JwtFilter; import cn.guet.server.mapper.MenuMapper; import cn.guet.server.mapper.RoleMapper; import cn.guet.server.pojo.Admin; import cn.guet.server.service.AdminService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import java.util.List; /** * @author pangjian * @ClassName SercutiyConfig * @Description Spring Security 配置 * @date 2021/6/2 22:18 */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private AdminService adminService; @Autowired private RestAuthorizationEntryPoint restAuthorizationEntryPoint; @Autowired private RestfulAccessDeniedHandler restfulAccessDeniedHandler; @Autowired private RoleMapper roleMapper; @Autowired private MenuMapper menuMapper; /** * @Description: 用自定义的UserDetailsService(自定义去数据库加载数据),PasswordEncoder(密码加密)的bean * @Param auth: * @return void * @date 2021/8/5 10:29 */ protected void configure(AuthenticationManagerBuilder auth) throws Exception{ auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder()); } /** * @Description: 可以跳过过滤器选中放行一部分接口 * @Param web: * @return void * @date 2021/6/14 20:38 */ @Override public void configure(WebSecurity web) throws Exception { // 放行一些访问路径 web.ignoring().antMatchers( "/css/**", "/js/**", "/index.html", "/favicon.ico", "/doc.html", "/webjars/**", "/swagger-resources/**", "/v2/api-docs/**" ); } /** * @Description:配置拦截规则 * @Param httpSecurity: * @return void * @date 2021/6/14 20:39 */ @Override protected void configure(HttpSecurity httpSecurity) throws Exception{ // 使用了JWT,先关掉csrf httpSecurity.csrf() .disable() // 基于token,不需要session .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/login","/logout") .permitAll() // 剩下所有请求都要求认证 .anyRequest() .authenticated() .and() // 禁用缓存 .headers() .cacheControl(); // 添加jwt 登录授权过滤器,在UsernamePasswordAuthenticationFilter前面执行 httpSecurity.addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class); // 添加自定义未授权和未登录结果返回 httpSecurity.exceptionHandling() .accessDeniedHandler(restfulAccessDeniedHandler) .authenticationEntryPoint(restAuthorizationEntryPoint); } /** * @Description:重写了loadUserByUsername方法,去数据库加载用户信息和用户权限,构建一个UserDetails接口实现类返回 * @return org.springframework.security.core.userdetails.UserDetailsService * @date 2021/6/14 15:58 */ @Override @Bean public UserDetailsService userDetailsService(){ return username-> { Admin admin = adminService.getAdminByUserName(username); if(null!=admin){ List<String> roleList = roleMapper.selectRoleByUsername(admin.getUsername()); List<String> authorities = menuMapper.selectMenuByRole(admin.getUsername()); authorities.addAll(roleList); admin.setAuthorities( AuthorityUtils.commaSeparatedStringToAuthorityList( String.join(",",authorities) ) ); // 该自定义的用户类已经实现了UserDetails接口,刚好符合了该方法的返回类型 return admin; } return null; }; } /** * @Description:密码加密 * @return org.springframework.security.crypto.password.PasswordEncoder * @date 2021/8/5 10:27 */ @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } /** * @Description:前置token过滤器 * @return cn.guet.server.Filter.JwtFilter * @date 2021/8/5 10:27 */ @Bean public JwtFilter jwtFilter(){ return new JwtFilter(); } }
package cn.guet.server.config; import cn.guet.server.RespObject.RespBean; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * @author pangjian * @ClassName RestfulAccessDeniedHandler * @Description 当访问接口没有权限时,自定义返回结果 * @date 2021/6/14 16:15 */ @Component public class RestfulAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setContentType("application/json"); PrintWriter writer = httpServletResponse.getWriter(); RespBean bean = RespBean.error("权限不足,请联系管理员"); bean.setCode(403); writer.write(new ObjectMapper().writeValueAsString(bean)); writer.flush(); writer.close(); } }
package cn.guet.server.config; import cn.guet.server.RespObject.RespBean; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * @author pangjian * @ClassName RestAuthorizetionEntryPoint * @Description 当未登录或者token失效时访问接口时,自定义返回结果 * @date 2021/6/14 16:08 */ @Component public class RestAuthorizationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setContentType("application/json"); PrintWriter printWriter = httpServletResponse.getWriter(); RespBean respBean = RespBean.error("尚未登录,请登录"); respBean.setCode(401); printWriter.write(new ObjectMapper().writeValueAsString(respBean)); printWriter.flush(); printWriter.close(); } }
目的是为了拦截token,并对它进行认证,确保当前请求用户已经登录和有相关权限访问此次要访问控制器方法
package cn.guet.server.Filter; import cn.guet.server.utils.JwtTokenUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author pangjian * @ClassName JwtFilter * @Description JWT 登录授权过滤器 * @date 2021/6/2 22:33 */ public class JwtFilter extends OncePerRequestFilter { @Value("${jwt.tokenHeader}") private String tokenHeader; @Value("${jwt.tokenHead}") private String tokenHead; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { // 先根据key,是否能拿到value String authHeader = httpServletRequest.getHeader(tokenHeader); // 判断登录用户的token不为空和是Bearer开头的 if(null!=authHeader && authHeader.startsWith(tokenHead)){ // 取到token String authToken = authHeader.substring(tokenHead.length()); // 从用户请求携带的token获取用户名,能取到证明token除了时间以外都合法了 String username = jwtTokenUtil.getUserNameFromToken(authToken); // token 存在用户名但没有认证的 if(null != username && null == SecurityContextHolder.getContext().getAuthentication()){ UserDetails userDetails = userDetailsService.loadUserByUsername(username); // 根据userDetails验证了token是否有效(验证时间是否过期和当前用户名是否匹配) if(jwtTokenUtil.validateToken(authToken,userDetails)){ // 我们的token,框架是不认识的,token有效就转化构建UsernamePasswordAuthenticationToken表示认证通过和进行相关授权 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails,null, userDetails.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)); // 设置了认证主体,到UsernamePasswordAuthenticationFilter就不会拦截,因为你应该带有了它的token SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } } // 继续执行其他过滤器 filterChain.doFilter(httpServletRequest,httpServletResponse); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。