Spring Boot+Spring Security + JWT集成

1、认证

1.1、配置登录地址

   ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests = hs.authorizeRequests();
   authorizeRequests.antMatchers("/login").anonymous();

1.2、初始化账号信息

初始化登录账号、密码、用户角色等信息。

    /**
     * 初始化登录用户信息
     */
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails userDetails = User.withUsername("admin") // 账号
                .password("$2a$10$nisfRsnoOqL2M44mZKlIQuYjTXPxYQBYM3dGMWfca1dYUBMV7e7iS") // `admin123`加密后字符串
                .roles("ADMIN") // 角色
                .build();

        return new InMemoryUserDetailsManager(userDetails);
    }

1.3、初始化账号信息

    @PostMapping(path = "/login", produces = APPLICATION_JSON_VALUE)
    public AjaxResult login(String username, String password) {
        if (isBlank(username)) {
            return AjaxResult.error("登录失败", "账号不可为空");
        }

        if (isBlank(password)) {
            return AjaxResult.error("登录失败", "密码不可为空");
        }

        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        if (isNull(userDetails) || !passwordEncoder.matches(password, userDetails.getPassword())) {
            return AjaxResult.error("登录失败", "账号或密码错误");
        }

        return AjaxResult.ok("登录成功", tokenService.createToken(username));
    }

2、授权

授权通过过滤器实现

    hs.addFilterAfter(new OncePerRequestFilter() {

        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            final String headerValue = request.getHeader(header);
            String username = null;
            if (isNotBlank(headerValue) && startsWith(headerValue, TOKEN_PREFIX)) {
                username = tokenService.getSubject(headerValue.replaceAll(TOKEN_PREFIX, EMPTY));
            }

            if (isNotBlank(username) && isNull(getContext().getAuthentication())) {
                UserDetails userDetails = userDetailsService().loadUserByUsername(username);

                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                getContext().setAuthentication(authentication);
            }

            filterChain.doFilter(request, response);
        }

    }, UsernamePasswordAuthenticationFilter.class);

3、防XSS攻击

3.1、解决方案

解决方案有多种,第一种就是自定一个过滤器,配置时请将这个 filter 放在第一位。
第二种,借助Spring提供的方式。

3.2、 自定义 BaseController.java 控制器,所有控制器继承该类

package com.cnblogs.javalouvre.web;

import static java.util.Objects.isNull;
import static org.apache.commons.lang3.StringUtils.EMPTY;

import java.beans.PropertyEditorSupport;

import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.util.HtmlUtils;

public class BaseController {

    @InitBinder
    public void initBinder(ServletRequestDataBinder binder) {
        binder.registerCustomEditor(String.class, new PropertyEditorSupport() {

            @Override
            public String getAsText() {
                Object text = getValue();

                return isNull(text) ? EMPTY : text.toString();
            }

            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                if (isNull(text)) {
                    setValue(null);
                } else {
                    setValue(HtmlUtils.htmlEscape(text));
                }
            }

        });
    }

}

4、设置跨域访问

4.1、注册 org.springframework.web.cors.CorsConfigurationSource 实例

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean("corsConfigurationSource")
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");// 设置访问源地址
        config.addAllowedHeader("*");// 设置访问源请求头
        config.addAllowedMethod("*");// 设置访问源请求方法
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);// 对接口配置跨域设置

        return configSource;
    }

}

4.2、设置开启跨域 hs.cors(Customizer.withDefaults())

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity hs) throws Exception {
    	// by default uses a Bean by the name of corsConfigurationSource
        hs.cors(Customizer.withDefaults());
    }

}

5、目录结构

│  pom.xml
│
└─src
    ├─main
    │  ├─java
    │  │  └─com
    │  │      └─cnblogs
    │  │          └─javalouvre
    │  │              │  App.java
    │  │              │
    │  │              ├─config
    │  │              │      SecurityConfig.java
    │  │              │
    │  │              ├─dto
    │  │              │      AjaxResult.java
    │  │              │
    │  │              ├─exception
    │  │              │      GlobalExceptionHandler.java
    │  │              │
    │  │              ├─service
    │  │              │      TokenService.java
    │  │              │
    │  │              ├─util
    │  │              │      Constants.java
    │  │              │      HttpUtils.java
    │  │              │      JacksonUtils.java
    │  │              │
    │  │              └─web
    │  │                      BaseController.java
    │  │                      IndexController.java
    │  │
    │  └─resources
    │          application.yml
    │
    └─test
        ├─java
        └─resources

6、完整代码

6.1、pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.12.RELEASE</version>
    <relativePath />
  </parent>
  <artifactId>spring-boot-springsecurity-jwt</artifactId>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
    </dependency>

    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
    </dependency>

    <!-- Spring Security -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- Spring Web -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- JWT -->
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt</artifactId>
      <version>0.9.1</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

6.2、src/main/java/com/cnblogs/javalouvre/App.java

package com.cnblogs.javalouvre;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }

}

6.3、src/main/java/com/cnblogs/javalouvre/config/SecurityConfig.java

package com.cnblogs.javalouvre.config;

import static com.cnblogs.javalouvre.dto.AjaxResult.forbidden;
import static com.cnblogs.javalouvre.util.Constants.TOKEN_PREFIX;
import static com.cnblogs.javalouvre.util.HttpUtils.getToken;
import static com.cnblogs.javalouvre.util.HttpUtils.write;
import static com.cnblogs.javalouvre.util.JacksonUtils.serialize;
import static java.util.Objects.isNull;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.startsWith;
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.security.core.context.SecurityContextHolder.getContext;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.Customizer;
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.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.OncePerRequestFilter;

import com.cnblogs.javalouvre.dto.AjaxResult;
import com.cnblogs.javalouvre.service.TokenService;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);

    @Override
    protected void configure(HttpSecurity hs) throws Exception {
        hs.csrf().disable();
        hs.exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPoint() {

            @Override
            public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                write(response, serialize(AjaxResult.class, forbidden("认证失败,无法访问该资源")));
            }

        });
        hs.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        hs.headers().frameOptions().disable();
        hs.cors(Customizer.withDefaults()); // by default uses a Bean by the name of corsConfigurationSource

        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests = hs.authorizeRequests();
        authorizeRequests.antMatchers("/login").anonymous();
        authorizeRequests.antMatchers(GET, "/*.html").permitAll();
        authorizeRequests.antMatchers(GET, "/**/*.js").permitAll();
        authorizeRequests.antMatchers(GET, "/**/*.css").permitAll();
        authorizeRequests.anyRequest().authenticated();

        hs.logout().logoutUrl("/logout").logoutSuccessHandler(new LogoutSuccessHandler() {

            @Override
            public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                String username = tokenService.getSubject(getToken(request, header));

                logger.info("username = {}", username);
            }

        });

        hs.addFilterAfter(new OncePerRequestFilter() {

            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
                final String headerValue = request.getHeader(header);
                String username = null;
                if (isNotBlank(headerValue) && startsWith(headerValue, TOKEN_PREFIX)) {
                    username = tokenService.getSubject(headerValue.replaceAll(TOKEN_PREFIX, EMPTY));
                }

                if (isNotBlank(username) && isNull(getContext().getAuthentication())) {
                    UserDetails userDetails = userDetailsService().loadUserByUsername(username);

                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                    getContext().setAuthentication(authentication);
                }

                filterChain.doFilter(request, response);
            }

        }, UsernamePasswordAuthenticationFilter.class);
    }

    /**
     * 认证
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 加密
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 初始化登录用户信息
     */
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails userDetails = User.withUsername("admin") // 账号
                .password("$2a$10$nisfRsnoOqL2M44mZKlIQuYjTXPxYQBYM3dGMWfca1dYUBMV7e7iS") // `admin123`加密后字符串
                .roles("ADMIN") // 角色
                .build();

        return new InMemoryUserDetailsManager(userDetails);
    }

    @Bean("corsConfigurationSource")
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");// 设置访问源地址
        config.addAllowedHeader("*");// 设置访问源请求头
        config.addAllowedMethod("*");// 设置访问源请求方法
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);// 对接口配置跨域设置

        return configSource;
    }

    @Autowired
    private TokenService tokenService;
    @Value("${token.header}")
    private String       header;
}

6.4、src/main/java/com/cnblogs/javalouvre/dto/AjaxResult.java

package com.cnblogs.javalouvre.dto;

import static java.util.Objects.nonNull;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_OK;

public class AjaxResult extends java.util.HashMap<String, Object> {

    private static final long   serialVersionUID = 242180064461040653L;

    // 状态码
    private static final String CODE_TAG         = "code";

    // 返回内容
    private static final String MSG_TAG          = "msg";

    // 数据对象
    private static final String DATA_TAG         = "data";

    public AjaxResult() {
        // DO NOTHING
    }

    public AjaxResult(int code, String msg) {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
    }

    public AjaxResult(int code, String msg, Object data) {
        this(code, msg);

        if (nonNull(data)) {
            super.put(DATA_TAG, data);
        }
    }

    public static AjaxResult ok() {
        return AjaxResult.ok("操作成功");
    }

    public static AjaxResult ok(Object object) {
        return AjaxResult.ok("操作成功", object);
    }

    public static AjaxResult ok(String msg) {
        return AjaxResult.ok(msg, null);
    }

    public static AjaxResult ok(String msg, Object data) {
        return new AjaxResult(SC_OK, msg, data);
    }

    public static AjaxResult error() {
        return AjaxResult.error("操作失败");
    }

    public static AjaxResult error(Object object) {
        return AjaxResult.error("操作失败", object);
    }

    public static AjaxResult error(String msg) {
        return AjaxResult.error(msg, null);
    }

    public static AjaxResult error(String msg, Object data) {
        return new AjaxResult(SC_INTERNAL_SERVER_ERROR, msg, data);
    }

    public static AjaxResult forbidden() {
        return AjaxResult.forbidden("操作禁止");
    }

    public static AjaxResult forbidden(Object object) {
        return AjaxResult.forbidden("操作禁止", object);
    }

    public static AjaxResult forbidden(String msg) {
        return AjaxResult.forbidden(msg, null);
    }

    public static AjaxResult forbidden(String msg, Object data) {
        return new AjaxResult(SC_FORBIDDEN, msg, data);
    }
}

6.5、src/main/java/com/cnblogs/javalouvre/exception/GlobalExceptionHandler.java

package com.cnblogs.javalouvre.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import com.cnblogs.javalouvre.dto.AjaxResult;

import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;

@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(SignatureException.class)
    @ResponseBody
    public AjaxResult signatureException(SignatureException e) {
        if (logger.isErrorEnabled()) {
            logger.error("Token为空", e);
        }

        return AjaxResult.error("Token为空");
    }

    @ResponseBody
    @ExceptionHandler(ExpiredJwtException.class)
    public AjaxResult expiredJwtException(ExpiredJwtException e) {
        if (logger.isErrorEnabled()) {
            logger.error("Token过期", e);
        }

        return AjaxResult.error("Token过期");
    }

    @ResponseBody
    @ExceptionHandler(MalformedJwtException.class)
    public AjaxResult malformedJwtException(MalformedJwtException e) {
        if (logger.isErrorEnabled()) {
            logger.error("Token数据错误", e);
        }

        return AjaxResult.error("Token数据错误");
    }
}

6.6、src/main/java/com/cnblogs/javalouvre/service/TokenService.java

package com.cnblogs.javalouvre.service;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

@Component
public class TokenService {

    /**
     * 生成Token
     *
     * @param sub
     * @return
     */
    public String createToken(String sub) {
        Calendar calendar = GregorianCalendar.getInstance();
        Date iat = calendar.getTime();
        calendar.add(GregorianCalendar.MINUTE, amount);
        Date exp = calendar.getTime();

        return Jwts.builder()//
                .setHeaderParam("typ", "JWT")// 令牌类型
                .setSubject(sub)// 主题
                .setIssuedAt(iat) // 签发时间
                .setExpiration(exp)// 过期时间
                .signWith(SignatureAlgorithm.HS512, secretKey)// 签名算法、秘钥
                .compact();
    }

    /**
     * 获取Token中注册信息
     *
     * @param token
     * @return
     */
    public Claims getBody(String token) {
        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();
    }

    /**
     * 获取签发日期
     *
     * @param token
     * @return
     */
    public Date getIssuedAt(String token) {
        return getBody(token).getIssuedAt();
    }

    /**
     * 获取过期时间
     *
     * @param token
     * @return
     */
    public Date getExpiration(String token) {
        return getBody(token).getExpiration();
    }

    /**
     * 获取主题信息
     *
     * @param token
     * @return
     */
    public String getSubject(String token) {
        return getBody(token).getSubject();
    }

    // 令牌秘钥
    @Value("${token.secret}")
    private String secretKey;

    // 令牌有效期(默认30分钟)
    @Value("${token.expireTime}")
    private int    amount;
}

6.7、src/main/java/com/cnblogs/javalouvre/util/Constants.java

package com.cnblogs.javalouvre.util;

/**
 * 常量
 */
public interface Constants {

    String TOKEN_PREFIX = "Bearer ";

}

6.8、src/main/java/com/cnblogs/javalouvre/util/HttpUtils.java

package com.cnblogs.javalouvre.util;

import static com.cnblogs.javalouvre.util.Constants.TOKEN_PREFIX;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.removeStart;
import static org.apache.commons.lang3.StringUtils.startsWith;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;

import io.jsonwebtoken.MalformedJwtException;

public final class HttpUtils {

    private HttpUtils() {
    }

    public static void write(HttpServletResponse response, String message) {
        try {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");
            PrintWriter out = response.getWriter();
            out.write(message);
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static String getToken(HttpServletRequest request, String headerName) {
        String headerValue = request.getHeader(headerName);
        if (isBlank(headerValue) || !startsWith(headerValue, TOKEN_PREFIX)) {
            throw new MalformedJwtException(StringUtils.EMPTY);
        }

        return removeStart(headerValue, TOKEN_PREFIX);
    }
}

6.9、src/main/java/com/cnblogs/javalouvre/util/JacksonUtils.java

package com.cnblogs.javalouvre.util;

import java.io.IOException;
import java.util.Objects;

import org.apache.commons.lang3.SerializationException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public final class JacksonUtils {

    private static final Logger       log          = LoggerFactory.getLogger(JacksonUtils.class);
    private static final ObjectMapper objectMapper = new ObjectMapper();

    private JacksonUtils() {
    }

    public static String serialize(Class<?> serializationView, Object value) {
        if (Objects.isNull(value)) {
            return StringUtils.EMPTY;
        }

        try {
            return objectMapper.writerWithView(serializationView).writeValueAsString(value);
        } catch (JsonProcessingException e) {
            log.error("COULD NOT WRITE JSON", e);

            throw new SerializationException("Could not write JSON: " + e.getMessage(), e);
        }
    }

    public static <T> T deserialize(Class<T> type, String src) {
        if (StringUtils.isBlank(src)) {
            return null;
        }

        try {
            return objectMapper.readerFor(type).readValue(src);
        } catch (IOException e) {
            log.error("COULD NOT READ JSON", e);

            throw new SerializationException("Could not read JSON: " + e.getMessage(), e);
        }
    }

}

6.10、src/main/java/com/cnblogs/javalouvre/web/BaseController.java

package com.cnblogs.javalouvre.web;

import static java.util.Objects.isNull;
import static org.apache.commons.lang3.StringUtils.EMPTY;

import java.beans.PropertyEditorSupport;

import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.util.HtmlUtils;

public class BaseController {

    @InitBinder
    public void initBinder(ServletRequestDataBinder binder) {
        binder.registerCustomEditor(String.class, new PropertyEditorSupport() {

            @Override
            public String getAsText() {
                Object text = getValue();

                return isNull(text) ? EMPTY : text.toString();
            }

            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                if (isNull(text)) {
                    setValue(null);
                } else {
                    setValue(HtmlUtils.htmlEscape(text));
                }
            }

        });
    }

}

6.11、src/main/java/com/cnblogs/javalouvre/web/IndexController.java

package com.cnblogs.javalouvre.web;

import static java.util.Objects.isNull;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import com.cnblogs.javalouvre.dto.AjaxResult;
import com.cnblogs.javalouvre.service.TokenService;
import com.cnblogs.javalouvre.util.HttpUtils;

@RestController
public class IndexController extends BaseController {

    @PostMapping(path = "/login", produces = APPLICATION_JSON_VALUE)
    public AjaxResult login(String username, String password) {
        if (isBlank(username)) {
            return AjaxResult.error("登录失败", "账号不可为空");
        }

        if (isBlank(password)) {
            return AjaxResult.error("登录失败", "密码不可为空");
        }

        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        if (isNull(userDetails) || !passwordEncoder.matches(password, userDetails.getPassword())) {
            return AjaxResult.error("登录失败", "账号或密码错误");
        }

        return AjaxResult.ok("登录成功", tokenService.createToken(username));
    }

    @PreAuthorize("hasAnyRole('ADMIN')")
    @GetMapping(path = "/index", produces = APPLICATION_JSON_VALUE)
    public AjaxResult execute(HttpServletRequest request) {
        return AjaxResult.ok(tokenService.getSubject(HttpUtils.getToken(request, header)));
    }

    @Autowired
    private PasswordEncoder    passwordEncoder;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private TokenService       tokenService;
    @Value("${token.header}")
    private String             header;
}

6.12、src/main/resources/application.yml

# token配置
token:
    # 令牌自定义标识
    header: Authorization
    # 令牌密钥(自定义)
    secret: 6F3931D35F0395DF82B032B8019AC57D
    # 令牌有效期(默认30分钟)
    expireTime: 30

logging:
  pattern:
    console: '%d{HH:mm:ss.SSS} %-5level %logger{20} - [%method,%line] - %msg%n'
  level:
    root: info
    '[org.springframework.beans.factory.annotation]': trace
    '[org.springframework.security.web]': trace
    '[org.springframework.web.servlet.mvc]': trace
    '[com.cnblogs.javalouvre]': trace
posted @ 2020-12-25 11:37  Bruce.Chang.Lee  阅读(472)  评论(0)    收藏  举报