springsecurity integrate JWT

1. The main principles: first log in. If login is successful, adding header of the response generated jwt token.

                     Then the customer at the time of initiating the request, verify header in the token is legitimate, if legitimate release, sub-rule returns an error message

2. dependence

        <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.9.1</version>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.56</version>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.9</version>
		</dependency>

3. Log filter, generates a token

import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
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.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.StreamUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.Charset;

public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {
    static final String TOKEN_PREFIX = "Bearer";
    static final String HEADER_STRING = "Authorization";
    private Logger log = LoggerFactory.getLogger(JWTLoginFilter.class);

    public JWTLoginFilter(AuthenticationManager authManager) {
        //配置只有访问/api/login时,这个过滤器才会起作用
        super(new AntPathRequestMatcher("/api/login", "POST"));
        setAuthenticationManager(authManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        JSONObject params;
        try {
            String jsonparam = StreamUtils.copyToString(request.getInputStream(), Charset.forName("utf-8"));
            params = JSONObject.parseObject(jsonparam);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new AuthenticationServiceException("读取登录请求参数异常");
        }


        String username = null;
        String password = null;
        if (params != null) {
            username = params.getString("username");
            password = params.getString("password");
        }

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

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

        username = username.trim();
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException {
        TokenAuthenticationHandler tokenAuthenticationHandler = new TokenAuthenticationHandler();
        Object obj = auth.getPrincipal();
        if (obj != null) {
            UserDetails userDetails = (UserDetails) obj;
            String token = tokenAuthenticationHandler.generateToken(JSONObject.toJSONString(userDetails));
            res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + token);
            //todo 将最新生成的token保存在redis或是数据库中,可以在JWTAuthenticationFilter中判断提交上来的token和当前数据库中的token是否一致
            //todo ,如果不一致则说明客户端没有使用最新的token

        }
    }
}

4. Verify token filter

import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JWTAuthenticationFilter extends GenericFilterBean {
    static final String HEADER_STRING = "Authorization";
    static final String TOKEN_PREFIX = "Bearer";

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) request;

        String token = req.getHeader(HEADER_STRING);
        if (StringUtils.isBlank(token) && req.getRequestURI().startsWith("/api")) {
            HttpServletResponse res = (HttpServletResponse) response;
            res.addHeader("Content-Type", "text/html;charset=UTF-8");
            res.setStatus(401);
            res.getWriter().write("{\"error\":\"请提供授权码\"}");
            return;
        }
        if (StringUtils.isNotBlank(token) && token.startsWith(TOKEN_PREFIX)) {
            //todo 从数据库中查询JWTLoginFilter生成的最新token,与当前提交的token对比,如果不一致,则返回token过期错误提示
            TokenAuthenticationHandler tokenAuthenticationHandler = new TokenAuthenticationHandler();
            String subject = tokenAuthenticationHandler.getSubjectFromToken(token.replace(TOKEN_PREFIX, ""));
            if (StringUtils.isBlank(subject)) {
                HttpServletResponse res = (HttpServletResponse) response;
                res.addHeader("Content-Type", "text/html;charset=UTF-8");
                res.setStatus(401);
                res.getWriter().write("{\"error\":\"授权码错误\"}");
                return;
            } else {
                //todo 校验token成功后,如果需要每次访问更新token,可以再次重新生成token,添加到响应头中
                //保存登录信息
                SecurityContextHolder.getContext().setAuthentication(new JWTAuthenticationToken(subject));
            }
        }
        filterChain.doFilter(request, response);
    }
}
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

import static java.util.Collections.emptyList;

public class JWTAuthenticationToken extends UsernamePasswordAuthenticationToken {

    public JWTAuthenticationToken(Object principal) {
        super(principal,null,emptyList());
    }

    @Override
    public Object getCredentials() {
        return super.getCredentials();
    }

    @Override
    public Object getPrincipal() {
        return super.getPrincipal();
    }
}
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class TokenAuthenticationHandler {

    private static final String CLAIM_KEY_CREATED = "created";
    private static final String CLAIM_KEY_SUBJECT = "subject";
    private static final String DEFAULT_SECRET = "@*secret@*";
    //有效期3天
    private static final Long DEFAULT_EXPIRATION = 259200L;

    public TokenAuthenticationHandler() {

    }

    public String getSubjectFromToken(String token) {
        String subject;
        try {
            final Claims claims = getClaimsFromToken(token);
            subject = claims.get(CLAIM_KEY_SUBJECT).toString();
        } catch (Exception e) {
            subject = null;
        }
        return subject;
    }

    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(DEFAULT_SECRET).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + DEFAULT_EXPIRATION * 1000);
    }

    public String generateToken(String subject) {
        Map<String, Object> claims = new HashMap<String, Object>();
        claims.put(CLAIM_KEY_CREATED, new Date());
        claims.put(CLAIM_KEY_SUBJECT, subject);
        return generateToken(claims);
    }

    public String generateToken(Map<String, Object> claims) {
        return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, DEFAULT_SECRET).compact();
    }
}

5.springsecurity Configuration

import com.example.demo.conf.security.JWTAuthenticationFilter;
import com.example.demo.conf.security.JWTLoginFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

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

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

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().passwordEncoder(passwordEncoder())
                .withUser("zhangsan").password("123456").roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .formLogin().loginPage("/toLogin")//自定义登录页面
                .loginProcessingUrl("/login")//登录提交表单url
                .defaultSuccessUrl("/index")//登录成功后页面
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                        httpServletResponse.addHeader("Content-Type", "text/html;charset=UTF-8");
                        if ("/api/login".equals(httpServletRequest.getRequestURI().toString())) {
                            httpServletResponse.getWriter().write("{\"error\":\"登录失败\"}");
                        } else {
                            httpServletResponse.getWriter().write("登录失败");
                        }
                    }
                })//登录失败处理
                .and().authorizeRequests()
                .antMatchers("/login").permitAll()//放行登录提交表单url
                .antMatchers("/toLogin").permitAll()//放行登录界面url
                .antMatchers("/api/login").permitAll()//放行api登录,获得jwt授权码
                .antMatchers("/**").authenticated() //所有路径都需要授权验证
                .and()
                .addFilterBefore(loginFilter(), UsernamePasswordAuthenticationFilter.class)//api登录处理逻辑过滤器
                .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);//验证token是否合法过滤器
    }

    public JWTLoginFilter loginFilter() throws Exception {
        JWTLoginFilter loginFilter = new JWTLoginFilter(authenticationManager());
        loginFilter.setAuthenticationFailureHandler((request, response, exception) -> {
            response.setContentType("application/json");
            response.getWriter().write("{\"errormsg\":\"jwt认证失败\"}");
        });
        loginFilter.setContinueChainBeforeSuccessfulAuthentication(false);
        return loginFilter;
    }

    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return charSequence.toString();
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return charSequence.toString().equals(s);
            }
        };
    }

}

Project Source Address: https://github.com/394938226/jwt_token

Guess you like

Origin blog.csdn.net/join_null/article/details/90601842
jwt