加入security+jwt安全策略
Pom中引入
<!-- security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
加入后访问页面会弹出一个登录页,用户名为user,密码为启动时显示的一串字符串
但是这种页面及默认用户显然不是我们想要的,我们需要用户数据持久化,也需要合理分配权限。如下我们开始对security做基本的设置。
关于security+jwt主要有如下几个类设置
1.新建JwtUserDetails实现UserDetails
package com.tangruo.example.common.security;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
/**
* 安全用户模型
* @author
*/
public class JwtUserDetails implements UserDetails {
private static final long serialVersionUID = 1L;
/**
* 用户名:这是数据库的字段,
* 是userName或者是account就写对应的字段
*/
private String username;
/**
* 密码
*/
private String password;
private String salt;
/**
* 权限集合
*/
private Collection<? extends GrantedAuthority> authorities;
JwtUserDetails(String username, String password, String salt, Collection<? extends GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.salt = salt;
this.authorities = authorities;
}
/**无论我数据库里的字段是 `account`,或者username,或者userName,或者其他代表账户的字段,
* 这里还是要写成 `getUsername()`,因为是继承的接口
*
* @return
*/
@Override
public String getUsername() {
return username;
}
@JsonIgnore
@Override
public String getPassword() {
return password;
}
public String getSalt() {
return salt;
}
/**
* 返回给用户的角色列表
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
/**
* 账户是否未过期
* @return
*/
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
*账户是否未锁定
* @return
*/
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
*密码是否未过期
* @return
*/
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
*账户是否激活
* @return
*/
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
}
2.新建UserDetailsServiceImpl实现UserDetailsService
package com.rexyn.common.security;
import com.rexyn.system.entity.User;
import com.rexyn.system.service.AuthorityService;
import com.rexyn.system.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 用户登录认证信息查询
*
* @author
*/
3.权限不足返回,新建AuthenticationAccessDeniedHandler实现AccessDeniedHandler类
package com.tangruo.example.common.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tangruo.example.common.api.ResultJson;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.httpclient.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
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
*/
@Component
@Slf4j
public class AuthenticationAccessDeniedHandler implements AccessDeniedHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
// 登陆状态下,权限不足执行该方法
// log.error("权限不足:" + accessDeniedException.getMessage());
response.setStatus(HttpStatus.SC_OK);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter printWriter = response.getWriter();
//printWriter.println(objectMapper.writeValueAsString(Result.fail(HttpStatus.SC_FORBIDDEN, accessDeniedException.getMessage())));
printWriter.println(objectMapper.writeValueAsString(ResultJson.fail(HttpStatus.SC_FORBIDDEN+"", "权限不足,无法访问")));
printWriter.flush();
printWriter.close();
}
}
4.登陆失效设置,新建JwtAuthenticationEntryPoint实现AuthenticationEntryPoint
package com.tangruo.example.common.security;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.httpclient.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tangruo.example.common.api.ResultJson;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
@Autowired
private ObjectMapper objectMapper;
/**
*
*/
private static final long serialVersionUID = -4957913354424675827L;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
// 验证为未登陆状态会进入此方法,认证错误
// log.error("认证失败,请求接口:{},请求IP:{},请求参数:{},失败原因:{}", request.getRequestURI(), WebUtil.getIP(request),
// JsonUtil.toJson(request.getParameterMap()), authException.getMessage());
response.setStatus(HttpStatus.SC_OK);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter printWriter = response.getWriter();
//printWriter.write(objectMapper.writeValueAsString(Result.fail(HttpStatus.SC_FORBIDDEN, authException.getMessage())));
printWriter.write(objectMapper.writeValueAsString(ResultJson.fail(HttpStatus.SC_FORBIDDEN+"", "登录已失效,请重新登录")));
printWriter.flush();
printWriter.close();
}
}
5.登陆验证规则重写,新建JwtAuthenticationProvider继承DaoAuthenticationProvider
package com.tangruo.example.common.security;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import com.tangruo.example.common.util.PasswordEncoder;
/**重写身份验证规则
* @author tangruo
*
* 2021年1月4日
*/
public class JwtAuthenticationProvider extends DaoAuthenticationProvider{
public JwtAuthenticationProvider(UserDetailsService userDetailsService) {
setUserDetailsService(userDetailsService);
}
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
String salt = ((JwtUserDetails) userDetails).getSalt();
// 覆写密码验证逻辑
if (!new PasswordEncoder(salt).matches(userDetails.getPassword(), presentedPassword)) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
6.新建WebSecurityConfig继承WebSecurityConfigurerAdapter
package com.tangruo.example.common.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
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.core.userdetails.UserDetailsService;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import com.tangruo.example.common.security.AuthenticationAccessDeniedHandler;
import com.tangruo.example.common.security.JwtAuthenticationEntryPoint;
import com.tangruo.example.common.security.JwtAuthenticationProvider;
/**
* 全局@author Sheldon
*/
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* Spring会自动寻找实现接口的类注入,会找到我们的 UserDetailsServiceImpl 类
*/
@Qualifier("userDetailsServiceImpl")
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private AuthenticationAccessDeniedHandler authenticationAccessDeniedHandler;
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// 使用自定义身份验证组件
auth.authenticationProvider(new JwtAuthenticationProvider