pom
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath />
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- springboot 整合mybatis框架 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- alibaba的druid数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.5</version>
</dependency>
<!--JWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.5.0</version>
</dependency>
</dependencies>
返回枚举
import lombok.Getter;
@Getter
public enum ResultEnum {
SUCCESS(101,"成功"),
FAILURE(102,"失败"),
USER_NEED_AUTHORITIES(201,"用户未登录"),
USER_LOGIN_FAILED(202,"用户账号或密码错误"),
USER_LOGIN_SUCCESS(203,"用户登录成功"),
USER_NO_ACCESS(204,"用户无权访问"),
USER_LOGOUT_SUCCESS(205,"用户登出成功"),
TOKEN_IS_BLACKLIST(206,"此token为黑名单"),
LOGIN_IS_OVERDUE(207,"登录已失效");
private Integer code;
private String message;
ResultEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
/**
* @author: zzx
* @date: 2018-10-15 16:26
* @deprecation:通过code返回枚举
*/
public static ResultEnum parse(int code){
ResultEnum[] values = values();
for (ResultEnum value : values) {
if(value.getCode() == code){
return value;
}
}
throw new RuntimeException("Unknown code of ResultEnum");
}
jwt工具类
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
//通过配置文件赋值expire、secret
public class TokenUtil {
/**
* 1800000
* token过期时间
*/
private static final long EXPIRE_TIME = 30*60*1000;
/**
* token秘钥
*/
private static final String TOKEN_SECRET = "JWT";
//生成jwt
public static String sign(String username){
try {
// 设置过期时间
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
// 私钥和加密算法
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
// 设置头部信息
Map<String, Object> header = new HashMap<>(2);
header.put("Type", "Jwt");
header.put("alg", "HS256");
// 返回token字符串
return JWT.create()
.withHeader(header)
.withClaim("username", username)
.withExpiresAt(date)
.sign(algorithm);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 检验token是否正确
* @param token 需要校验的token
* @return 校验是否成功
*/
public static DecodedJWT verify(String token){
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt;
try {
jwt = verifier.verify(token);
}catch (Exception e){
return null;
}
return jwt;
}
}
user类
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 用户表
*/
public class UserModel implements UserDetails, Serializable {
private static final long serialVersionUID = 7171722954972237961L;
private Integer id;
private String username;
private String realname;
private String password;
private Date createDate;
private Date lastLoginTime;
private List<GrantedAuthority> authorities = new ArrayList<>();
@Override
public List<GrantedAuthority> getAuthorities() {
return authorities;
}
public void setAuthorities(List<GrantedAuthority> authorities) {
this.authorities = authorities;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return 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;
}
public void setUsername(String username) {
this.username = username;
}
public String getRealname() {
return realname;
}
public void setRealname(String realname) {
this.realname = realname;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Date getLastLoginTime() {
return lastLoginTime;
}
public void setLastLoginTime(Date lastLoginTime) {
this.lastLoginTime = lastLoginTime;
}
}
无权访问
import com.alibaba.fastjson.JSON;
import com.my.Enum.ResultEnum;
import com.my.model.ResultModel;
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;
/**
* @description: 无权访问
*/
@Component
public class AjaxAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.getWriter().write(JSON.toJSONString(ResultModel.result(ResultEnum.USER_NO_ACCESS,false)));
}
}
未登录
import com.alibaba.fastjson.JSON;
import com.my.Enum.ResultEnum;
import com.my.model.ResultModel;
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;
/**
* @description: 用户未登录时返回给前端的数据
*/
@Component
public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.getWriter().write(JSON.toJSONString(ResultModel.result(ResultEnum.USER_NEED_AUTHORITIES, false)));
}
}
登陆失败
import com.alibaba.fastjson.JSON;
import com.my.Enum.ResultEnum;
import com.my.model.ResultModel;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @description: 用户登录失败时返回给前端的数据
*/
@Component
public class AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.getWriter().write(JSON.toJSONString(ResultModel.result(ResultEnum.USER_LOGIN_FAILED,false)));
}
}
登陆成功
import com.alibaba.fastjson.JSON;
import com.my.Enum.ResultEnum;
import com.my.model.ResultModel;
import com.my.model.UserModel;
import com.my.utils.TokenUtil;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @description: 用户登录成功时返回给前端的数据
*/
@Component
public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
//待处理
UserModel userModel = (UserModel) authentication.getPrincipal();
String jwtToken = TokenUtil.sign(userModel.getUsername());
httpServletResponse.getWriter().write(JSON.toJSONString(ResultModel.result(ResultEnum.USER_LOGIN_SUCCESS,jwtToken,true)));
}
}
退出
import com.alibaba.fastjson.JSON;
import com.my.Enum.ResultEnum;
import com.my.model.ResultModel;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author: zzx
* @date: 2018/10/16 9:59
* @description: 登出成功
*/
@Component
public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.getWriter().write(JSON.toJSONString(ResultModel.result(ResultEnum.USER_LOGOUT_SUCCESS,true)));
}
}
jwt拦截器
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.my.service.serviceimpl.MemberDetailsServiceimpl;
import com.my.utils.TokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
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.Base64;
/**
* @description: jwt拦截器
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
MemberDetailsServiceimpl memberDetailsServiceimpl;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
//检查有没有token
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String authToken = authHeader.substring("Bearer ".length());
DecodedJWT verify = TokenUtil.verify(authToken);
//token为null代表token存在问题
if(verify != null){
//获取username
String payload = verify.getPayload();
JSONObject jsonObject = JSON.parseObject(new String(Base64.getDecoder().decode(payload.getBytes())));
String username = jsonObject.getString("username");
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//根据username获取具体角色进行绑定
UserDetails userDetails = memberDetailsServiceimpl.loadUserByUsername(username);
if (userDetails != null) {
//authentication添加用户信息
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
}
filterChain.doFilter(request, response);
}
}
重写UserDetailsService.loadUserByUsername方法
import com.my.model.UserModel;
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.Component;
import java.util.ArrayList;
import java.util.List;
/**
* 重写UserDetailsService.loadUserByUsername方法
*/
@Component
public class MemberDetailsServiceimpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
//根据userName获取用户信息,这里就不调用数据库了
UserModel user = new UserModel();
user.setId(1);
user.setPassword("$2a$10$5PqKbvXXcF/vpgd7KRnx2./39rdIaPS4E1E0yRFqdbBZHeaX0.CPO");
user.setUsername("mykt-admin");
user.setRealname("张三");
if(user == null){
return null;
}
//创建权限集合
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
/**
* 这个admin权限应该要根据用户名从数据库总获取到角色
* 通过用户获取角色并进行绑定
*/
grantedAuthorities.add(new SimpleGrantedAuthority("admin"));
//将角色绑定到user
user.setAuthorities(grantedAuthorities);
return user;
}
}
实时权限控制
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @description: 权限访问控制
*/
@Component("rbacauthorityservice")
public class RbacAuthorityService {
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
Object userInfo = authentication.getPrincipal();
boolean hasPermission = false;
/**
* 这个地方有个坑,就是如果你不这样判断,随便请求一个接口会进入到这个方法,那么如果
* 不带正确的token或不带token就会报类转换异常,所以这里要用instanceof判断是否可以强转
*/
if(userInfo instanceof UserDetails){
//获取角色
Collection<? extends GrantedAuthority> authorities = ((UserDetails) userInfo).getAuthorities();
//角色可能存在多个
List<String> userList = new ArrayList<>();
for (GrantedAuthority authority : authorities) {
userList.add(authority.toString());
}
/**
* 根据角色获取获取具体url权限,就省略了
*/
Set<String> urls = new HashSet();
//模拟一下,就不涉及到数据库了
urls.add("/demo/**");
//判断请求的url是否匹配
AntPathMatcher antPathMatcher = new AntPathMatcher();
for (String url : urls) {
if (antPathMatcher.match(url, request.getRequestURI())) {
hasPermission = true;
break;
}
}
return hasPermission;
}else{
return false;
}
}
}
security配置
import com.my.Filter.JwtAuthenticationTokenFilter;
import com.my.Handler.AjaxAccessDeniedHandler;
import com.my.Handler.AjaxAuthenticationEntryPoint;
import com.my.Handler.AjaxAuthenticationFailureHandler;
import com.my.Handler.AjaxAuthenticationSuccessHandler;
import com.my.Handler.AjaxLogoutSuccessHandler;
import com.my.service.serviceimpl.MemberDetailsServiceimpl;
import org.springframework.beans.factory.annotation.Autowired;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
AjaxAuthenticationEntryPoint authenticationEntryPoint;//未登陆时返回 JSON 格式的数据给前端(否则为 html)
@Autowired
AjaxAuthenticationSuccessHandler authenticationSuccessHandler; //登录成功返回的 JSON 格式数据给前端(否则为 html)
@Autowired
AjaxAuthenticationFailureHandler authenticationFailureHandler; //登录失败返回的 JSON 格式数据给前端(否则为 html)
@Autowired
AjaxLogoutSuccessHandler logoutSuccessHandler;//注销成功返回的 JSON 格式数据给前端(否则为 登录时的 html)
@Autowired
AjaxAccessDeniedHandler accessDeniedHandler;//无权访问返回的 JSON 格式数据给前端(否则为 403 html 页面)
@Autowired
JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // JWT 拦截器
@Autowired
private MemberDetailsServiceimpl memberDetailsServiceimpl;
/**
* 新增Security
* 授权账户
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(memberDetailsServiceimpl).passwordEncoder(new BCryptPasswordEncoder());
}
/**
* 认证方式
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 去掉 CSRF
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 使用 JWT,关闭token
.and()
.httpBasic().authenticationEntryPoint(authenticationEntryPoint)
.and()
.authorizeRequests()//定义哪些URL需要被保护、哪些不需要被保护
.antMatchers("/test/register")//允许匿名注册
.permitAll()
.anyRequest()//任何请求,登录后可以访问
.access("@rbacauthorityservice.hasPermission(request,authentication)") // RBAC 动态 url 认证
.and()
.formLogin() //开启登录, 定义当需要用户登录时候,转到的登录页面
.loginPage("/login")
.loginProcessingUrl("/login")
.successHandler(authenticationSuccessHandler) // 登录成功
.failureHandler(authenticationFailureHandler) // 登录失败
.permitAll()
.and()
.logout()//默认注销行为为logout
.logoutUrl("/logout")
.logoutSuccessHandler(logoutSuccessHandler)
.permitAll();
// 记住我
http.rememberMe().rememberMeParameter("remember-me")
.userDetailsService(memberDetailsServiceimpl).tokenValiditySeconds(1000);
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // 无权访问 JSON 格式的数据
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // JWT Filter
}
}
yml
server:
port: 8080
tomcat:
uri-encoding: utf-8
servlet:
context-path: /demo
login.ftl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登陆</title>
</head>
<body>
<h1>自定义登陆页面</h1>
<form action="login" method="post">
<span>用户名称</span><input type="text" name="username" /><br>
<span>用户密码</span><input type="password" name="password" /><br>
<span><input type="checkbox" name="remember-me">记住密码<br>
<input type="submit" value="登陆">
</form>
<#if RequestParameters['error']??>
用户名称或者密码错误
</#if>
</body>
</html>
login自定义请求
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* login 自定义页面请求
*/
@Controller
public class LoginController {
@RequestMapping("/login")
public String login(){
return "login";
}
}
注意事项
1.密码需要通过BCryptPasswordEncoder中encode方法进行加密,会出现:Encoded password does not look like BCryp
2.自定义登陆页面action=“/login”,这个地方如果存在斜杆就会出现404
3.如果存在验证码和注册相关的一些接口,就将url添加到Security配置
效果演示
现在登陆成功,根据token调用相关接口试试
源码
链接:https://pan.baidu.com/s/1SuzJP3FIeZDfrcyLzhOj2Q
提取码:yyds
里面代码可能存在数据库相关代码,删除即可
我参考的文章:https://blog.csdn.net/zzxzzxhao/article/details/83381876