Spring Security总结(二)

在Spring Boot中使用Spring Security

一、引入依赖

我是利用maven来构建项目的,因此需要在maven中引入Spring Security的以来,在pom.xml中引入依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

二、自定义Spring Security配置

1. 返回给前端的格式

返回的result都以json的格式给前端

Result.java

import java.io.Serializable;

/**
 * @Auther:kiritoghy
 * @Desc:用于返回对应消息
 * @Date:19-7-23 下午2:05
 */
public class Result implements Serializable {
    int code;
    String message;
    Object data;

    public Result() {
    }

    public Result(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "Result{" +
                "code=" + code +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }
}

ResultGenerator.java

import org.omg.PortableInterceptor.SUCCESSFUL;

/**
 * @Auther:kiritoghy
 * @Date:19-7-23 下午6:00
 */
public class ResultGenerator {
    private static final int SUCCESS_CODE = 200;
    private static int FAIL_CODE = 500;
    private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";
    private static final String DEFAULT_FAIL_MESSAGE = "FAIL";

    /**
     * 生成默认成功信息
     * @return
     */
    public static Result genSuccessResult(){
        Result result = new Result();
        result.setCode(SUCCESS_CODE);
        result.setMessage(DEFAULT_SUCCESS_MESSAGE);
        return result;
    }

    /**
     * 生成指定成功信息
     * @param message
     * @return
     */
    public static Result genSuccessResult(String message){
        Result result = new Result();
        result.setCode(SUCCESS_CODE);
        result.setMessage(message);
        return result;
    }

    /**
     * 生成带有返回数据的成功信息
     * @param data
     * @return
     */
    public static Result genSuccessResult(Object data){
        Result result = new Result();
        result.setCode(SUCCESS_CODE);
        result.setMessage(DEFAULT_SUCCESS_MESSAGE);
        result.setData(data);
        return result;
    }

    /**
     * 生成默认失败信息
     * @return
     */
    public static Result genFailResult(){
        Result result = new Result();
        result.setCode(FAIL_CODE);
        result.setMessage(DEFAULT_FAIL_MESSAGE);
        return result;
    }

    /**
     * 生成指定失败信息
     * @param message
     * @return
     */
    public static Result genFailResult(String message){
        Result result = new Result();
        result.setCode(FAIL_CODE);
        result.setMessage(message);
        return result;
    }

    /**
     * 生成带有数据的失败信息
     * @param data
     * @return
     */
    public static Result genFailResult(Object data){
        Result result = new Result();
        result.setCode(FAIL_CODE);
        result.setMessage(DEFAULT_FAIL_MESSAGE);
        result.setData(data);
        return result;
    }
}

2. 用户自定义user

SelfUserDetails继承了UserDetails接口,用来认证用户的信息

SelfUserDetails.java

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Set;

/**
 * @Auther: kiritoghy
 * @Date: 19-7-23 下午7:20
 */
public class SelfUserDetails implements UserDetails {

    private Long id;
    private String username;
    private String password;
    private Set<? extends GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    public void setAuthorities(Set<? extends GrantedAuthority> authorities) {
        this.authorities = authorities;
    }

    @Override
    public String getPassword() { // 最重点Ⅰ
        return this.password;
    }

    @Override
    public String getUsername() { // 最重点Ⅱ
        return this.username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    //账号是否过期
    @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 Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

3. SelfUserServiceImpl

SelfUserDetailsService继承了UserDetailsService,实现loadUserByUsername方法

SelfUserServiceImpl.java

import com.uestc.labelproject.dao.UserMapper;
import com.uestc.labelproject.entity.SelfUserDetails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
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.HashSet;
import java.util.Set;

/**
 * @Auther: kiritoghy
 * @Date: 19-7-23 下午7:24
 */
@Component
public class SelfUserServiceImpl implements UserDetailsService {

    @Autowired
    UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        SelfUserDetails user = userMapper.getUser(username);

        if(user == null){
            throw new BadCredentialsException("该用户不存在");
        }

        String authorities = userMapper.getAuthoritiesById(user.getId());
        Set authoritiesSet = new HashSet();
        GrantedAuthority authority = new SimpleGrantedAuthority(authorities);
        authoritiesSet.add(authority);
        user.setAuthorities(authoritiesSet);

        return user;
    }
}

该方法从数据库获取到user信息,放入SelfUserDetails中

4. CustomAuthenticationEntryPoint

CustomAuthenticationEntryPoint实现AuthenticationEntryPoint接口,处理用户未登录

CustomAuthenticationEntryPoint.java

import com.alibaba.fastjson.JSON;
import com.uestc.labelproject.utils.ResultGenerator;
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.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Auther: kiritoghy
 * @Desc:入口处理,用于未登录等
 * @Date: 19-7-23 下午6:50
 */
@Component
@Slf4j
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        log.info("This is CustomAuthenticationEntryPoint");
        httpServletResponse.setContentType("application/json;charset=utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(ResultGenerator.genFailResult("用户未认证")));
    }
}

5. CustomAuthenticationSuccessHandler

CustomAuthenticationSuccessHandler.java

实现AuthenticationSuccessHandler接口,处理登录成功的情况

import com.alibaba.fastjson.JSON;
import com.uestc.labelproject.entity.SelfUserDetails;
import com.uestc.labelproject.entity.User;
import com.uestc.labelproject.service.AdminUserService;
import com.uestc.labelproject.utils.AccessAddressUtil;
import com.uestc.labelproject.utils.JwtTokenUtil;
import com.uestc.labelproject.utils.RedisUtil;
import com.uestc.labelproject.utils.ResultGenerator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
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.util.HashMap;
import java.util.Map;

/**
 * @Auther: kiritoghy
 * @Desc:登录成功处理器
 * @Date: 19-7-23 下午6:56
 */
@Slf4j
@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Value("${token.expirationSeconds}")
    private int expirationSeconds;

    @Value("${token.validTime}")
    private int validTime;

    @Autowired
    RedisUtil redisUtil;
    @Autowired
    AdminUserService adminUserService;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        String ip = AccessAddressUtil.getIpAddress(httpServletRequest);
        Map<String,Object> map = new HashMap<>();
        map.put("ip",ip);

        SelfUserDetails selfUserDetails = (SelfUserDetails) authentication.getPrincipal();

        String token = JwtTokenUtil.generateToken(selfUserDetails.getUsername(), expirationSeconds, map);

        Integer expire = validTime*24*60*60*1000;
        User user = adminUserService.getUserById(selfUserDetails.getId());
        user.setPassword(null);
        String currentIp = AccessAddressUtil.getIpAddress(httpServletRequest);
        redisUtil.setTokenRefresh(token,selfUserDetails.getUsername(),currentIp);
        log.info("用户{}登录成功,信息已保存至redis",selfUserDetails.getUsername());
        Map<String,Object> res = new HashMap<>();
        res.put("token", token);
        res.put("user", user);
        httpServletResponse.setContentType("application/json;charset=utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(ResultGenerator.genSuccessResult(res)));
    }
}

6. CustomAuthenticationFailureHandler

实现AuthenticationFailureHandler接口,处理用户登录失败

CustomAuthenticationFailureHandler.java

import com.alibaba.fastjson.JSON;
import com.uestc.labelproject.utils.ResultGenerator;
import lombok.extern.slf4j.Slf4j;
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;

/**
 * @Auther: kiritoghy
 * @Desc:登录失败处理器
 * @Date: 19-7-23 下午7:57
 */
@Slf4j
@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        log.info("Try login Failed");
        httpServletResponse.setContentType("application/json;charset=utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(ResultGenerator.genFailResult()));
    }
}

7. CustomAccessDeniedHandler

实现AccessDeniedHandler接口,处理无权登录的情况

CustomAccessDeniedHandler.java

import com.alibaba.fastjson.JSON;
import com.uestc.labelproject.utils.ResultGenerator;
import lombok.extern.slf4j.Slf4j;
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;

/**
 * @Auther: kiritoghy
 * @Desc:权限不足处理
 * @Date: 19-7-23 下午9:59
 */
@Component
@Slf4j
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        log.info("用户权限不够");
        httpServletResponse.setContentType("application/json;charset=utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(ResultGenerator.genFailResult("用户权限不足")));
    }
}

### 8. CustomLogoutSuccessHandler
实现LogoutSuccessHandler接口,处理退出成功
#### CustomLogoutSuccessHandler.java
```java
import com.alibaba.fastjson.JSON;
import com.uestc.labelproject.utils.DateUtil;
import com.uestc.labelproject.utils.RedisUtil;
import com.uestc.labelproject.utils.ResultGenerator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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;

/**
 * @Auther: kiritoghy
 * @Desc:登出
 * @Date: 19-7-23 下午8:03
 */
@Slf4j
@Component
public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {

    @Autowired
    RedisUtil redisUtil;

    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        String authHeader = httpServletRequest.getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            final String authToken = authHeader.substring("Bearer ".length());
            //将token放入黑名单中
            redisUtil.hset("blacklist", authToken, DateUtil.getTime());
            log.info("用户登出成功!token:{}已加入redis黑名单",authToken);
        }
        httpServletResponse.setContentType("application/json;charset=utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(ResultGenerator.genSuccessResult("Logout Success!")));
    }
}

9. CustomAuthenticationProvider

实现AuthenticationProvider接口,用户登录判断,可用于自定义登录

CustomAuthenticationProvider.java

import com.uestc.labelproject.entity.SelfUserDetails;
import com.uestc.labelproject.service.impl.SelfUserServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * @Auther: kiritoghy
 * @Desc:用户登录判断,可用于自定义登录方式
 * @Date: 19-7-23 下午9:27
 */
@Component
@Slf4j
public class CustomAuthenticationProvider  implements AuthenticationProvider {

    @Autowired
    SelfUserServiceImpl selfUserService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = (String)authentication.getCredentials();
        SelfUserDetails selfUserDetails = (SelfUserDetails) selfUserService.loadUserByUsername(username);
        log.info("用户请求的账户密码:{}/{}",username,password);
        log.info("数据库的密码:{}",selfUserDetails.getPassword());
        if(!password.equals(selfUserDetails.getPassword())){
            throw new BadCredentialsException("密码不正确");
        }
        log.info("用户权限:{}",selfUserDetails.getAuthorities());
        return new UsernamePasswordAuthenticationToken(selfUserDetails,password,selfUserDetails.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return aClass.equals(UsernamePasswordAuthenticationToken.class);
    }
}

以上为基本的自定义配置文件,其中关于jwt token的部分以及redis的部分下次再总结