基于springBoot+springSecurity+jwt实现前后端分离用户权限认证
1. 项目说明
主要基于前后端分离情况下用户权限认证, 当用户登录认证成功后,每个用户会获取到自己的token,在请求其他接口时只需携带token即可,后端会通过token来识别用户身份。springSecurity也有很多种权限认证方式,本项目主要实现基于接口授权,也就是说通过注解给controller赋予权限,用户只有拥有某个接口的权限才能成功访问这个接口,从而实现不同用户拥有不同访问权限;
2. 项目结构
1、 使用maven引入依赖在pom.xml中添加以下依赖(springSecurity+jwt)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2、通过 application.properties配置项目系统库和jwt认证
spring.datasource.mg.cboard.url=jdbc:mysql://192.168.0.65:3306/pharos?useUnicode=true&characterEncoding=UTF-8
spring.datasource.mg.cboard.username=pharos
spring.datasource.mg.cboard.password=Ketech@123456
spring.datasource.mg.cboard.driver-class-name=com.mysql.cj.jdbc.Driver
jwt.tokenHeader: Authorization
#JWT存储的请求头
jwt.secret: ketech-pharos
#JWT加解密使用的密钥
jwt.expiration: 604800
#JWT的超期限时间(60*60*24*7)
jwt.tokenHead: 'Bearer '
#JWT负载中拿到开头
3、 用户权限关系表导入数据库
** 表结构在项目源码中可自行下载
4、 编写jwt工具类 主要功能是:用户信息加密,除了登录认证时账号密码需要明文传输,其他地方都需要加密后传输,通过jwt对用户加密后形成token,用来进行用户认证。添加token时效,每个用户请求接口不是永久的,如果是永久的那么他就没有存在的意义了;token验证,token有自己加密协议, 来保证接口安全;token解密,将token翻译成明文信息;
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
/**
* 创建token
* @param username 用户名
* @return
*/
public String generateToken(String username) {
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512, secret)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.compact();
}
/**
* 从token中获取用户名
* @param token
* @return
*/
public String getUserNameFromToken(String token){
return getTokenBody(token).getSubject();
}
/**
* 是否已过期
* @param token
* @return
*/
public boolean isExpiration(String token){
return getTokenBody(token).getExpiration().before(new Date());
}
private Claims getTokenBody(String token){
Claims claims=null;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
throw new DescribeException(ExceptionEnum.valid_token);
}
return claims;
}
}
4、 配置springSecurity 1 简介 他的核心功能是: 1认证:这个地方需要整合jwt对用户进行认证 2 授权:认证完成后,在数据库中获取用户的权限。接口会有自己的权限,只有当用户权限和接口所需权能匹配时,用户才能成功访问接口; 3 过滤器:其核心是一条过滤链,只有当每个filter通过了才能请求到接口,这个中间过程需要自己配置,并不是所有接口都需要权限,比如登录接口,异常接口 ;
2 核心代码简介
1> 用户拦截
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsServiceImpl userDetailsServices;
// @Autowired
// FavUserDetailService favUserDetailService;
/**
* 验证码拦截器
*/
// @Autowired
// private VerifyCodeFilter verifyCodeFilter;
/**
* 登录成功逻辑
*/
@Autowired
AuthenticationSuccessHandlerImp authenticationSuccessHandler;
/**
* 登录失败逻辑
*/
@Autowired
AuthenticationFailureHandlerImp authenticationFailureHandler;
// /**
// * 无权限拦截器
// */
// @Autowired
// RestAuthenticationEntryPoint restAuthenticationEntryPoint;
/**
* 无权访问 JSON 格式的数据
*/
@Autowired
private AccessDeniedHandlerImp accessDeniedHandler;
@Autowired
private AuthenticationEntryPointImp authenticationEntryPointImp;
@Override
public void configure(WebSecurity web) throws Exception {
//放行静态资源
web.ignoring().antMatchers("/v2/api-docs",
"/swagger-resources/configuration/ui",
"/swagger-resources",
"/swagger-resources/configuration/security",
"/swagger-ui.html")
.antMatchers(HttpMethod.POST, "/user/login","/swagger-ui.html");
}
/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//
// http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class);
//允许跨域访问
http.cors();
//放行swagger
http.authorizeRequests()
.antMatchers("/v2/api-docs",//swagger api json
"/swagger-resources/configuration/ui",//用来获取支持的动作
"/swagger-resources",//用来获取api-docs的URI
"/swagger-resources/configuration/security",//安全选项
"/swagger-ui.html",
"/webjars/**").permitAll();
//关闭csrf
http.csrf().disable()
.sessionManagement()// 基于token,所以不需要session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
//未登陆时返回 JSON 格式的数据给前端
.httpBasic().authenticationEntryPoint(authenticationEntryPointImp)
.and()
.authorizeRequests()
//任何人都能访问这个请求
.antMatchers("/swagger-ui.html","/user/login","/user/get").permitAll()
//除上面配置的路径外,所有的请求都需要进行认证后才能访问
.anyRequest().authenticated()
.and()
.formLogin()
// (登录页面或是提示用户未登录的页面)
.loginPage("/user/get").permitAll()
//认证通过后的事件处理(在这里返回token)
.loginProcessingUrl("/user/login")
// 登录成功
.successHandler(authenticationSuccessHandler)
// 登录失败
.failureHandler(authenticationFailureHandler)
.permitAll()
.and()
//设置登录注销url,这个无需我们开发,springsecurity已帮我们做好
.logout().logoutUrl("/user/logout").permitAll()
.and()
//配置 记住我 功能,
.rememberMe().rememberMeParameter("rememberme");
// 防止iframe 造成跨域
// .and()
// .headers()
// .frameOptions()
// .disable();
http.httpBasic().disable();
//.and().httpBasic(); //开启HTTP Basic后 ,可用postman通过携带用户名+密码直接请求接口
// 禁用缓存
http.headers().cacheControl();
// 添加自定义 JWT 过滤器
http.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
// 无权访问 JSON 格式的数据
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
//没有认证时,在这里处理结果,不要重定向
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPointImp);
}
@Bean
public JwtAuthenticationTokenFilters jwtAuthenticationTokenFilter() {
return new JwtAuthenticationTokenFilters();
}
// //密码编码器
// @Bean
// public PasswordEncoder passwordEncoder(){
// return NoOpPasswordEncoder.getInstance();
// }
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
/**
* 身份认证接口
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServices).passwordEncoder(passwordEncoder());
}
}
1 @EnableWebSecurity
利用@EnableWebSecurity 注解继承 WebSecurityConfigurerAdapter的类,这样就构成了 Spring Security 的配置;
2 @EnableGlobalMethodSecurity(prePostEnabled = true)
配置基于接口授权
3 WebSecurityConfigurerAdapter
通过继承次类重写他的方法来配置权限拦截;
HttpSecurity常用方法和说明:
2> 用户授权 前面我们通过jwt对用户进行了认证,WebSecurityConfigurerAdapter对通过认证的用户进行权限过滤,接下来需要实现UserDetailsService的loadUserByUsername方法对用户进行授权;
@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
UserService userService;
/**
* 根据用户名获取用户 - 用户的角色、权限等信息
*/
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
UserDetails userDetails = null;
try {
PharosUser user = userService.getUserByName(userName);
List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
List<MenuIndexDto> list = userService.listByUserId(user.getUserId());
for (MenuIndexDto dto : list) {
String authority = dto.getPermission();
if (!("").equals(authority) & authority != null) {
authList.add(new SimpleGrantedAuthority(dto.getPermission()));
}
}
userDetails = new User(user.getUserName(), user.getPassword(), true, true, true, true, authList);
} catch (Exception e) {
e.printStackTrace();
}
return userDetails;
}
}
5 postma测试 1 获取通过用户名+密码获取token
2 访问需要权限的接口
3 访问用户没有访问权限的接口