Springboot+SpringSecurity 权限管理笔记
一、导入Springsecurity 依赖
<!--security 权限框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
二、配置白名单路径
(一)在 application.yml 中配置放行的路径
application.yml
secure:
ignored:
urls: #安全路径白名单
- /swagger-ui.html
- /swagger-ui/**
- /swagger-resources/**
- /swagger/**
- /v3/**
- /**/*.js
- /**/*.css
- /**/*.png
- /**/*.ico
- /webjars/**
- /actuator/**
- /druid/**
- /**/common/**
- /common/**
(二)IgnoreUrlsConfig 类——获取放行路径
IgnoreUrlsConfig
package com.cloud.wl.hp.config.security;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.List;
/**
* SpringSecurity白名单资源路径配置
*
* @author wanglin
* @version 1.0
* @date 2022-02-28 周一
*/
@Getter
@Setter
@ConfigurationProperties(prefix = "secure.ignored")
public class IgnoreUrlsConfig {
private List<String> urls = new ArrayList<>();
}
三、编写 SecurityConfig 配置类
(一)SecurityConfig 配置类
package com.cloud.wl.hp.config.security;
import com.cloud.wl.hp.config.component.JwtAccessDeniedHandler;
import com.cloud.wl.hp.config.component.JwtAuthenticationEntryPoint;
import com.cloud.wl.hp.config.component.JwtAuthenticationTokenFilter;
import com.cloud.wl.hp.config.model.LoginUserDetails;
import com.cloud.wl.hp.domain.SysUser;
import com.cloud.wl.hp.service.CommonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
* 对SpringSecurity配置类的扩展,支持自定义白名单资源路径和查询用户逻辑
*
* @author wanglin
* @version 1.0
* @date 2022-01-14 周五
* @description
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CommonService commonService;
@Autowired
private JwtAccessDeniedHandler jwtAccessDeniedHandler;
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Override
@Bean
public UserDetailsService userDetailsService() {
return username -> {
SysUser user = commonService.getLoginUserInfo(username);
Set<SimpleGrantedAuthority> authorities = new HashSet<>();
authorities.add(new SimpleGrantedAuthority("admin"));
LoginUserDetails userDetails = new LoginUserDetails(user, authorities);
if (Objects.nonNull(user)) {
return userDetails;
}
return null;
};
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
// 密码加密方式
return new BCryptPasswordEncoder();
}
@Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
return new JwtAuthenticationTokenFilter();
}
/**
* 实现自己的登录逻辑
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(bCryptPasswordEncoder());
}
//方式二:
// @Override
// public void configure(WebSecurity web) {
放行静态资源
// web.ignoring()
// .antMatchers(HttpMethod.GET,
// "/swagger-resources/**",
// "/common/**",
// "/component/**",
// "/**/*.html",
// "/**/*.css",
// "/**/*.js",
// "/swagger-ui.html",
// "/webjars/**",
// "/v3/**",
// "/swagger/**");
// }
@Override
protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
// 不需要保护的资源路径允许访问
for (String url : ignoreUrlsConfig().getUrls()) {
registry.antMatchers(url).permitAll();
}
registry.and()
// http
// 禁用 CSRF
.csrf().disable()
// .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
// 允许跨域的OPTIONS请求
.antMatchers(HttpMethod.OPTIONS).permitAll()
// 放行登录、注册等
// .antMatchers("/**/common/**").permitAll()
// .antMatchers("/**/user/page").hasAnyAuthority("admin","user")
// 自定义匿名访问所有url放行 : 允许匿名和带权限以及登录用户访问
// .antMatchers(anonymousUrls.toArray(new String[0])).permitAll()
// 其他任何请求都需要身份认证
.anyRequest().authenticated()
.and()
// .addFilter(new JwtAuthenticationFilter(authenticationManager()))
// .addFilter(new JwtAuthorizationFilter(authenticationManager()))
// 不需要session,不创建会话
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class)
// 添加自定义未授权和未登录返回结果
.exceptionHandling()
.accessDeniedHandler(jwtAccessDeniedHandler)
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
// 防止iframe 造成跨域
.and()
.headers()
.frameOptions()
.disable();
}
@Bean
public IgnoreUrlsConfig ignoreUrlsConfig() {
return new IgnoreUrlsConfig();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
四、自定义JWT登录授权过滤器
(一)JwtAuthenticationTokenFilter 配置类
package com.cloud.wl.hp.config.component;
import com.cloud.wl.hp.base.utils.JwtTokenUtil;
import org.apache.commons.lang3.StringUtils;
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;
import java.util.Objects;
/**
* jwt 登录授权过滤器
*
* @author wanglin
* @version 1.0
* @date 2022-03-02 周三
*/
public class JwtAuthenticationTokenFilter 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 request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader(tokenHeader);
//存在token
String authToken = authHeader;
if (StringUtils.isNotBlank(authHeader)) {
//判断token是否是以 'Bearer ' 开头
if (authHeader.startsWith(tokenHead)) {
//截取'Bearer ' 后面的token
authToken = authHeader.substring(tokenHead.length());
}
String username = jwtTokenUtil.getUserNameFromToken(authToken);
//token 存在用户名,但未登录
if (StringUtils.isNotBlank(username) && Objects.isNull(SecurityContextHolder.getContext().getAuthentication())) {
//登录
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
//验证token是否有效,重新设置用户对象
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
}
filterChain.doFilter(request, response);
}
}
LoginUserDetails 类
五、自定义未授权和未登录返回结果
(一)自定义未授权返回类
JwtAccessDeniedHandler 类
package com.cloud.wl.hp.config.component;
import com.cloud.wl.hp.config.utils.SecurityUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 没有访问权限
*
* @author wanglin
* @version 1.0
* @date 2022-02-28 周一
*/
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException {
SecurityUtils.resultFailMsg(response, HttpServletResponse.SC_FORBIDDEN, "权限不足,请联系管理员!", e.getMessage());
}
}
(二)未登录返回类
JwtAuthenticationEntryPoint 类
package com.cloud.wl.hp.config.component;
import com.cloud.wl.hp.config.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 当为登录或者token无效时访问接口,自定义返回结果
*
* @author wanglin
* @version 1.0
* @date 2022-02-28 周一
*/
@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
SecurityUtils.resultFailMsg(response, HttpServletResponse.SC_UNAUTHORIZED, "未登录,请登录!", authException.getMessage());
}
}
(三)SecurityUtils 工具类
package com.cloud.wl.hp.config.utils;
import com.cloud.wl.hp.base.common.mapper.JsonMapperUtil;
import com.cloud.wl.hp.base.dto.RestResultDTO;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author wanglin
* @version 1.0
* @date 2022-01-19 周三
*/
public class SecurityUtils {
/**
* security 统一返回信息处理
*
* @param response
* @param code
* @param msg
* @throws IOException
*/
public static void resultSuccessMsg(HttpServletResponse response, Integer code, Object data, String msg) throws IOException {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Cache-Control", "no-cache");
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(code);
RestResultDTO result = new RestResultDTO<>(code, msg, data, null);
response.getWriter().write(JsonMapperUtil.toJson(result));
response.getWriter().flush();
}
/**
* security 统一返回信息处理
*
* @param response
* @param code
* @param msg
* @param exceptionMsg
* @throws IOException
*/
public static void resultFailMsg(HttpServletResponse response, Integer code, String msg, String exceptionMsg) throws IOException {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Cache-Control", "no-cache");
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(code);
RestResultDTO result = new RestResultDTO<>(code, msg, null, exceptionMsg);
response.getWriter().write(JsonMapperUtil.toJson(result));
response.getWriter().flush();
}
/**
* security 统一返回信息处理
*
* @param response
* @param code
* @param msg
* @throws IOException
*/
public static void resultFailMsg(HttpServletResponse response, Integer code, String msg) throws IOException {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Cache-Control", "no-cache");
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(code);
RestResultDTO result = new RestResultDTO<>(code, msg, null, null);
response.getWriter().write(JsonMapperUtil.toJson(result));
response.getWriter().flush();
}
/**
* security 统一返回信息处理
*
* @param response
* @param msg
* @throws IOException
*/
public static void resultFailMsg(HttpServletResponse response, String msg) throws IOException {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Cache-Control", "no-cache");
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
RestResultDTO result = new RestResultDTO<>(0, msg, null, null);
response.getWriter().write(JsonMapperUtil.toJson(result));
response.getWriter().flush();
}
}
六、自定义登录类 LoginUserDetails
(一)LoginUserDetails类——实现UserDetails类
LoginUserDetails 类
package com.cloud.wl.hp.config.model;
import com.cloud.wl.hp.domain.SysUser;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.util.Collection;
import java.util.Set;
/**
* @author wanglin
* @version 1.0
* @date 2022-02-28 周一
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUserDetails implements UserDetails {
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
private String username;
private String password;
//实现类 SimpleGrantedAuthority
// private Collection<? extends GrantedAuthority> authorities;
private Set<SimpleGrantedAuthority> authorities;
//只要有一个为false,就不可用用
private Boolean isAccountNonExpired;
private Boolean isAccountNonLocked;
private Boolean isCredentialsNonExpired;
private Boolean isEnabled;
public LoginUserDetails(SysUser user, Set<SimpleGrantedAuthority> authorities) {
this.username = user.getUserId();
this.password = user.getPassword();
// this.password = bCryptPasswordEncoder.encode(user.getPassword());
// this.authorities = Collections.singleton(new SimpleGrantedAuthority(user.getRoleType()));
this.authorities = authorities;
this.isEnabled = user.getIsEnabled();
this.isAccountNonExpired = user.getIsAccountNonExpired();
this.isAccountNonLocked = user.getIsAccountNonLocked();
this.isCredentialsNonExpired = user.getIsCredentialsNonExpired();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
(二)SysUser类
SysUser类
package com.cloud.wl.hp.domain;
import com.baomidou.mybatisplus.annotation.TableName;
import com.cloud.wl.hp.base.model.AbstractBaseDeleteSecurityModel;
import com.cloud.wl.hp.support.utils.Constants;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Table;
import javax.persistence.Column;
import javax.persistence.Entity;
/**
* @author wanglin
* @version 1.0
* @date 2022-01-04 周二
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Entity(name = SysUser.TABLE_NAME)
@Table(appliesTo = SysUser.TABLE_NAME, comment = "用户表")
@TableName(value = SysUser.TABLE_NAME)
public class SysUser extends AbstractBaseDeleteSecurityModel {
public static final String TABLE_NAME = Constants.TABLE_PREFIX + "sys_user";
@Column(columnDefinition = "varchar(32) comment '用户账号'", nullable = false)
private String userId;
@Column(columnDefinition = "varchar(50) comment '用户类型,参见RoleType枚举:admin,user'", nullable = false)
private String roleType;
@Column(columnDefinition = "varchar(20) comment '姓名'", nullable = false)
private String name;
@Column(columnDefinition = "varchar(20) comment '昵称'")
private String nickname;
@Column(columnDefinition = "varchar(20) comment '身份证号'")
private String idNo;
@Column(columnDefinition = "varchar(5) comment '性别'")
private String sex;
@Column(columnDefinition = "varchar(20) comment '手机号'")
private String mobileNo;
@Column(columnDefinition = "varchar(64) comment '密码'", nullable = false)
private String password;
@Column(columnDefinition = "varchar(30) comment '邮箱'")
private String email;
@Column(columnDefinition = "varchar(300) comment '登录凭证'")
private String loginToken;
@Column(columnDefinition = "int(1) not null default 0 comment '状态,0:正常 ;1:禁用'")
private Integer status;
@Column(columnDefinition = "bit not null default 1 comment '是否首次登录'")
private Boolean isFirstLogin;
@Column(columnDefinition = "varchar(200) comment '头像'")
private String icon;
@Column(columnDefinition = "bit not null default 0 comment '是否为VIP'")
private Boolean isVip;
@Column(columnDefinition = "varchar(200) comment '备注信息'")
private String note;
}
关注林哥,持续更新哦!!!★,°:.☆( ̄▽ ̄)/$:.°★ 。