Spring Security 基于 SESSION 的授权认证

Spring Security 基于 SESSION 权限认证

这个项目只是一个阶段性的总结,细节方面做的不是很好(代码很乱)

首先要说明的是,其实到这一步就可以结束 Spring Security 的学习了,就目前而言,我们用这个授权认证已经很不错了,后面那些比如 JWT、OAuth2 都不是必须的,绝大多数的 Web 项目都是基于 SESSION 认证的,又有多少个企业的项目横跨浏览器、手机端呢?而且我有点搞不清楚到底要怎么去做前后端分离,现在我这个项目虽然都是返回的 JSON 数据,但是我感觉加了 Spring Security 比以前复杂了不知道多少呢。

项目说明

理论上来说,这是一个很简单的项目,就是一个登陆注册权限认证,外加数据库访问...

所以用到的技术点有:Spring Boot、Spring Security、Spring Data Jpa。

然后整个项目结构如下:

实体类

这些实体类...大概都是必须的吧...

用户

这里需要说明的是这个用户只是针对于 Spring Security 需要的属性,个人项目的话肯定还需要其他的东西,比如邮箱、性别、年龄等等,所以只是一个参照吧,这其中还有一个 Role 和 Authorities,权限和角色我也有点不知道该用哪个,如果我们的项目只分角色的话,那我觉得用一个一对一的关联就好了。

package io.xuqu.entity;

import lombok.Data;

import javax.persistence.*;

@Data
@Entity(name = "t_user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @Column(nullable = false, unique = true, length = 32)
    private String username;

    @Column(nullable = false, length = 100)
    private String password;

    //    private String authority;
    @OneToOne
    private Role Role;    // 以单一角色替代多权限

    @Column(nullable = false)
    private boolean accountNonExpired;

    @Column(nullable = false)
    private boolean accountNonLocked;

    @Column(nullable = false)
    private boolean credentialsNonExpired;

    @Column(nullable = false)
    private boolean enabled;
}

UserVO

这个使用来视图层登录和注册数据封闭的一个实体类(更方便吧)

package io.xuqu.entity;

import lombok.Data;

@Data
public class UserVO {
    private String username;
    private String password;
}

角色

看到这个类就知道我这个项目代码到底写得多差了...

package io.xuqu.entity;

import lombok.Data;

import javax.persistence.*;

@Data
@Entity(name = "t_role")
public class Role {
    public static final String ROLE_USER_NAME = "ROLE_USER";
    public static final String ROLE_USER_DETAIL = "Just ordinary users, have access to specific /user and public interfaces.";
    public static final String ROLE_ADMIN_NAME = "ROLE_ADMIN";
    public static final String ROLE_ADMIN_DETAIL = "The system administrator, which can be assigned by the developer, is the role with the most permissions to a certain extent.";

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @Column(nullable = false, length = 20, unique = true)
    private String name;

    private String detail;

    public static Role USER() {
        Role role = new Role();
        role.setId(1);
        role.setName(ROLE_USER_NAME);
        role.setDetail(ROLE_USER_DETAIL);
        return role;
    }

    public static Role ADMIN() {
        Role role = new Role();
        role.setId(2);
        role.setName(ROLE_ADMIN_NAME);
        role.setDetail(ROLE_ADMIN_DETAIL);
        return role;
    }

}

统一结果

package io.xuqu.entity;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class ResponseEntity<T> {

    private Integer code;
    private String message;
    private T data;

}

记住我Token

哎,这标题都不知道该怎么写了,我觉得这个类有点多余...

package io.xuqu.entity;

import lombok.Data;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.Date;

@Data
@Entity(name = "t_persistent_remember_me_token")
public class PersistentRememberMeTokenEntity {
    @Id
    @Column(nullable = false, length = 64)
    private String series;

    @Column(nullable = false, length = 64)
    private String username;

    @Column(nullable = false, length = 64)
    private String token;

    @Column(nullable = false)
    private Date lastUsed;
}

UserDetails

其实我最开始是用 User 直接继承 UserDetails 的,但是后来测试的时候发现注册成功后返回数据我不能够将角色这一栏置为空,就很麻烦,还是写了一个这个类,主要是用于 Spring Security 的用户验证授权。

package io.xuqu.entity;

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

import java.util.Collection;
import java.util.List;

public class MyUserDetails implements UserDetails {

    private final User user;

    public MyUserDetails(User user) {
        this.user = user;
    }


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority(user.getRole().getName()));
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return user.isAccountNonExpired();
    }

    @Override
    public boolean isAccountNonLocked() {
        return user.isAccountNonLocked();
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return user.isCredentialsNonExpired();
    }

    @Override
    public boolean isEnabled() {
        return user.isEnabled();
    }
}

持久层

到这里的话,就真的很乱。我也不知道应该怎么做,比如对于角色这个 Repository 只用到了一次,我还是开了一个类,就基本上没有手动写过数据库吧。

TokenRepository

这个类就是我们程序的记住我功能需要实现的一些方法(也就是对数据库的增删改查吧),这里 Spring Security 其实提供了一个实现类(通过 Jdbc),为了让项目统一使用 Spring Data Jpa 就只能自己实现了。

package io.xuqu.repository;

import io.xuqu.entity.PersistentRememberMeTokenEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.Objects;

@Slf4j
@Repository
@Transactional
public class MyPersistentTokenRepository implements PersistentTokenRepository {

    private PersistentRememberMeTokenRepository tokenRepository;

    @Autowired
    public void setTokenRepository(PersistentRememberMeTokenRepository tokenRepository) {
        this.tokenRepository = tokenRepository;
    }

    @Override
    public void createNewToken(PersistentRememberMeToken persistentRememberMeToken) {
        PersistentRememberMeTokenEntity persistentRememberMeTokenEntity = new PersistentRememberMeTokenEntity();
        persistentRememberMeTokenEntity.setToken(persistentRememberMeToken.getTokenValue());
        persistentRememberMeTokenEntity.setUsername(persistentRememberMeToken.getUsername());
        persistentRememberMeTokenEntity.setSeries(persistentRememberMeToken.getSeries());
        persistentRememberMeTokenEntity.setLastUsed(persistentRememberMeToken.getDate());
        tokenRepository.save(persistentRememberMeTokenEntity);
    }

    @Override
    public void updateToken(String series, String tokenValue, Date lastUsed) {
        // 多出一步查询操作...(我不知道 JPA 的保存会不会忽略空值)
        PersistentRememberMeTokenEntity persistentRememberMeTokenEntity = tokenRepository.findBySeries(series);
        persistentRememberMeTokenEntity.setLastUsed(lastUsed);  // 更新最后使用时间
        persistentRememberMeTokenEntity.setToken(tokenValue);   // 更新 Token
        tokenRepository.save(persistentRememberMeTokenEntity);
    }

    @Override
    public PersistentRememberMeToken getTokenForSeries(String series) {
        // 查询
        PersistentRememberMeTokenEntity persistentRememberMeTokenEntity = tokenRepository.findBySeries(series);

        /*
            结果为空、多结果集、数据库连接错误
            JPA 好像不会查出多个吧...
            数据库连接错误我就不深究了
         */
        if (Objects.isNull(persistentRememberMeTokenEntity)) {
            if (log.isDebugEnabled()) {
                log.debug("Querying token for series {} returned no results.", series);
            }
        }
        // 略
        else {
            String username = persistentRememberMeTokenEntity.getUsername();
            String token = persistentRememberMeTokenEntity.getToken();
            Date lastUsed = persistentRememberMeTokenEntity.getLastUsed();
            return new PersistentRememberMeToken(username, series, token, lastUsed);
        }

        // 没查到应该返回 null ,上面也只是打印一些日志....
        return null;
    }

    @Override
    public void removeUserTokens(String username) {
        tokenRepository.deleteByUsername(username);
    }
}

下面才是真正做数据库查询操作的

package io.xuqu.repository;

import io.xuqu.entity.PersistentRememberMeTokenEntity;
import org.springframework.data.repository.CrudRepository;


public interface PersistentRememberMeTokenRepository extends CrudRepository<PersistentRememberMeTokenEntity, String> {
    void deleteByUsername(String username);
    PersistentRememberMeTokenEntity findBySeries(String series);
}

用户和角色

看到这个类我就感觉有点大材小用了...

package io.xuqu.repository;

import io.xuqu.entity.Role;
import org.springframework.data.repository.CrudRepository;

public interface RoleRepository extends CrudRepository<Role, Integer> {

}
package io.xuqu.repository;

import io.xuqu.entity.User;
import org.springframework.data.repository.CrudRepository;

import java.util.Optional;

public interface UserRepository extends CrudRepository<User, Integer> {
    Optional<User> findByUsername(String username);
}

业务层

这里只有一个类,也就是操作我们的 UserDetails ,同样代码写的很菜。

package io.xuqu.service;

import io.xuqu.entity.MyUserDetails;
import io.xuqu.entity.Role;
import io.xuqu.entity.User;
import io.xuqu.entity.UserVO;
import io.xuqu.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class MyUserDetailService implements UserDetailsService {

    private UserRepository userRepository;
    private PasswordEncoder passwordEncoder;

    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Autowired
    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<User> user = userRepository.findByUsername(username);
        // 如果 user 为空则抛出异常
        user.orElseThrow(() -> new UsernameNotFoundException("Not found: " + username));

        return new MyUserDetails(user.get());
    }

    public User register(UserVO userVO) {
        // 普通用户注册
        User user = new User();
        user.setUsername(userVO.getUsername());
        user.setPassword(passwordEncoder.encode(userVO.getPassword()));
        user.setRole(Role.USER());
        user.setEnabled(true);
        user.setAccountNonExpired(true);
        user.setAccountNonLocked(true);
        user.setCredentialsNonExpired(true);

        Optional<User> byUsername = userRepository.findByUsername(user.getUsername());
        if (byUsername.isPresent()) {
            return null;
        }

        return userRepository.save(user);
    }
}

控制类

毫无疑问的是,这种小项目也能写出这种菜鸡代码也就只有菜鸡水平了...

package io.xuqu.controller;

import io.xuqu.entity.ResponseEntity;
import io.xuqu.entity.User;
import io.xuqu.entity.UserVO;
import io.xuqu.service.MyUserDetailService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.Objects;

@RestController
public class MainController {

    private MyUserDetailService userDetailService;

    @Autowired
    public void setUserDetailService(MyUserDetailService userDetailService) {
        this.userDetailService = userDetailService;
    }

    public static ResponseEntity<Object> send(Object data) {
        return ResponseEntity.builder()
                .code(HttpStatus.OK.value())
                .message(HttpStatus.OK.getReasonPhrase())
                .data(data)
                .build();
    }

    @GetMapping("/")
    public ResponseEntity<Object> sayHello(@RequestParam(value = "name", required = false) String name) {
        return send(String.format("Hello, %s!", StringUtils.isBlank(name) ? "World扥东方" : name));
    }

    @GetMapping("/user")
    public ResponseEntity<Object> forUser(@RequestParam(value = "name", required = false) String name) {
        return send(String.format("Hello, %s!", StringUtils.isBlank(name) ? "User" : name));
    }

    @GetMapping("/admin")
    public ResponseEntity<Object> forAdmin(@RequestParam(value = "name", required = false) String name) {
        return send(String.format("Hello, %s!", StringUtils.isBlank(name) ? "Admin" : name));
    }

    @GetMapping("/again")
    public ResponseEntity<Object> doAgain() {
        return send("Hello Again");
    }

    @PostMapping("/register")
    public ResponseEntity<Object> doRegister(@RequestBody UserVO userVO) {
        if (Objects.isNull(userVO)) {
            return ResponseEntity.builder()
                    .code(HttpStatus.BAD_REQUEST.value())
                    .message(HttpStatus.BAD_REQUEST.getReasonPhrase())
                    .data(null)
                    .build();
        } else {
            if (Objects.isNull(userVO.getUsername()) || Objects.isNull(userVO.getPassword())) {
                return ResponseEntity.builder()
                        .code(HttpStatus.BAD_REQUEST.value())
                        .message("Username or password cannot be left blank")
                        .data(null)
                        .build();
            }
            User register = userDetailService.register(userVO);

            if (Objects.isNull(register)) {
                return ResponseEntity.builder()
                        .code(HttpStatus.BAD_REQUEST.value())
                        .message("Username already exists")
                        .data(null)
                        .build();
            }

            // 隐藏密码和角色(虽然没多大用处...)
            register.setPassword(null);
            register.setRole(null);

            return ResponseEntity.builder()
                    .code(HttpStatus.OK.value())
                    .message(HttpStatus.OK.getReasonPhrase())
                    .data(register)
                    .build();
        }
    }

}

配置

这或许才是重点吧,我觉得这里的代码写得菜也没办法...Spring Security 并没有提供给我们返回 JSON 数据的实现呀。

SecurityConfiguration

别的不说,这个链式编程还是挺舒服的。

package io.xuqu.config;

import io.xuqu.repository.MyPersistentTokenRepository;
import io.xuqu.service.MyUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;


@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private MyUserDetailService userDetailService;
    private MyPersistentTokenRepository persistentTokenRepository;

    @Autowired
    public void setUserDetailService(MyUserDetailService userDetailService) {
        this.userDetailService = userDetailService;
    }

    @Autowired
    public void setPersistentTokenRepository(MyPersistentTokenRepository persistentTokenRepository) {
        this.persistentTokenRepository = persistentTokenRepository;
    }

    /*
     * 用户名和密码认证过滤器
     * 不推荐使用了...因为 remember-me 和 logout 都会失效
     * @return  增加了可判断 json 形式数据的自修改过滤器
     * @throws Exception    获取认证管理器对象的时候抛出异常
     */
//    @Bean
//    public MyUsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter() throws Exception {
//        MyUsernamePasswordAuthenticationFilter filter = new MyUsernamePasswordAuthenticationFilter();
//        filter.setFilterProcessesUrl("/login");
//        filter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
//        filter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
//        filter.setAuthenticationManager(authenticationManagerBean());
//        return filter;
//    }

    /**
     * 用户密码的编码器
     * @return 默认构造器的盐加密
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        http.addFilterAfter(usernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        http.authorizeRequests()
                .antMatchers("/", "/register").permitAll()
                .antMatchers("/user").hasAnyRole("USER", "ADMIN")
                .antMatchers("/admin").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .exceptionHandling()
                .accessDeniedHandler(new MyAccessDeniedHandler())
                .authenticationEntryPoint(new MyAuthenticationEntryPoint())
                .and()
                .formLogin()
                .successHandler(new MyAuthenticationSuccessHandler())
                .failureHandler(new MyAuthenticationFailureHandler())
                .and()
                .logout()
                .logoutSuccessHandler(new MyLogoutSuccessHandler())
                .and()
                .rememberMe()
                .alwaysRemember(true)   // 默认开启 remember-me
                .tokenValiditySeconds(60 * 60) // 默认是十四天
                .tokenRepository(persistentTokenRepository)
                .userDetailsService(userDetailService)  // 可以不配置
                .and()
                .httpBasic()
                .and()
                .csrf().disable();
    }
}

登录处理器

这些处理器都差不多...我的业务逻辑并不复杂

成功

package io.xuqu.config;

import io.xuqu.entity.ResponseEntity;
import io.xuqu.util.SecurityHandlerResponseUtil;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        ResponseEntity<Object> responseEntity = ResponseEntity.builder()
                .code(HttpStatus.OK.value())
                .message(HttpStatus.OK.getReasonPhrase())
                .data(authentication.getPrincipal())
                .build();
        SecurityHandlerResponseUtil.send(response, responseEntity);
    }
}

失败

package io.xuqu.config;

import io.xuqu.entity.ResponseEntity;
import io.xuqu.util.SecurityHandlerResponseUtil;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
        ResponseEntity<Object> responseEntity = ResponseEntity.builder()
                .code(HttpStatus.BAD_REQUEST.value())
                .message(e.getMessage())
                .data(null)
                .build();
        SecurityHandlerResponseUtil.send(response, responseEntity);
    }
}

登出处理器

package io.xuqu.config;

import io.xuqu.entity.ResponseEntity;
import io.xuqu.util.SecurityHandlerResponseUtil;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        Object data = Objects.isNull(authentication) ? "You have not logged" : authentication.getDetails();

        ResponseEntity<Object> responseEntity = ResponseEntity.builder()
            .code(HttpStatus.OK.value())
            .message(HttpStatus.OK.getReasonPhrase())
            .data(data)
            .build();

        SecurityHandlerResponseUtil.send(response, responseEntity);
    }
}

未认证

这是登陆之后无权限处理

package io.xuqu.config;

import io.xuqu.entity.ResponseEntity;
import io.xuqu.util.SecurityHandlerResponseUtil;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException {
        ResponseEntity<Object> responseEntity = ResponseEntity.builder()
                .code(HttpStatus.UNAUTHORIZED.value())
                .message(e.getMessage())
                .data(null)
                .build();
        SecurityHandlerResponseUtil.send(response, responseEntity);
    }
}

这是登录之前无权限处理

package io.xuqu.config;

import io.xuqu.entity.ResponseEntity;
import io.xuqu.util.SecurityHandlerResponseUtil;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
        ResponseEntity<Object> responseEntity = ResponseEntity.builder()
                .code(HttpStatus.UNAUTHORIZED.value())
                .message(e.getMessage())
                .data(null)
                .build();
        SecurityHandlerResponseUtil.send(response, responseEntity);
    }
}

用户登陆过滤器

这个的话,我本意是想要项目登录可以使用表单数据也可以使用 JSON 数据,但是后来发现用自己的过滤器之后注销登录和记住我功能都失效了,资料不足,我也没找到办法解决,所以就让它过时了。

package io.xuqu.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.xuqu.entity.UserVO;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

@Deprecated
public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String contentType = request.getContentType();
        if (MediaType.APPLICATION_JSON_VALUE.equals(contentType) ||
                // 可能存在 application/json;UTF-8 和 ...;utf-8 两种情况
                new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8).toString().equalsIgnoreCase(contentType)) {

            if (!request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException(
                        "Authentication method not supported: " + request.getMethod());
            }

            UserVO userVO = null;

            try {
                ObjectMapper mapper = new ObjectMapper();
                ServletInputStream inputStream = request.getInputStream();
                userVO = mapper.readValue(inputStream, UserVO.class);

            } catch (IOException e) {
                e.printStackTrace();
            }

            String username = "";
            String password = "";

            if (Objects.nonNull(userVO)) {
                username = userVO.getUsername();
                password = userVO.getPassword();
            }

            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }

            username = username.trim();

            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                    username, password);

            // Allow subclasses to set the "details" property
            setDetails(request, authRequest);

            return super.getAuthenticationManager().authenticate(authRequest);
        } else {
            // 如果不是 json 形式的数据则交给父类处理
            return super.attemptAuthentication(request, response);
        }
    }
}

工具类

毫无意义!

package io.xuqu.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class SecurityHandlerResponseUtil {
    private static final ObjectMapper mapper = new ObjectMapper();

    public static void send(HttpServletResponse response, Object data) throws IOException {
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        String responseStr = mapper.writeValueAsString(data);
        PrintWriter writer = response.getWriter();
        writer.println(responseStr);
        writer.close();
    }
}

总结

这个项目还是让我学会了很多的,其实这个我是拼凑起来的,可以看一下我整个项目的结构:

在前面的学习过程中已经学会了如何简单配置 Authentication 和 Authorization ,这里我也都直接复制粘贴了,还有就是使用JDBC只是一笔带过,我觉得现在也许没有还在用JDBC的新项目吧(其实我还是挺喜欢那种方式的,简单)。然后是返回 JSON 数据的各种处理器的配置,还有记住我这个功能的实现,统一结果的封装,确确实实学到了不少。

不过 JWT 和 OAuth2 我还没有学习,有点累了,这三天都在看这个 Spring Security ,但是资料实在太少了,这方面的教程感觉都不是太好,有的甚至就讲一下基础的知识,有的还在用xml配置,有的版本也过于低了,有的还在用jsp...我都不知道写了那些到底有啥用,每个教程必备的就是自定义登录页面,我寻思你直接教我们如何不用登录也没都好一些呀,你教一个jsp登录页面有啥用?我能够学到什么呢?不过我还是在B站找到了一个不错的教程,是一个外国人的,后来在 Youtube 上面看了两遍(有自动的字幕),个人觉得还是学到了不少东西。

总的来说我后面可能暂时不会去了解其他东西了,Spring Security 这部分的内容足够我接下来至少半年的时间学习使用了。还有前面我花了两天时间学习的 Thymeleaf 则是半年可能都用不到,实在不知道该怎么说呀,前后端分离说得好听,各种教程又还在用模板引擎做示例,而且就我个人而言同时开发前后端项目实在有点吃不消,前不久开了一个 TodeDev 项目就不怎么做得下去了,前端的东西我学得比较少,而且我也不是很喜欢,我有点强迫症,做页面太难了...

就这样吧,接下来可能会去了解 Spring Cloud,也就是微服务,只是先了解吧。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://blog.imoyb.com/archives/spring-securit-session