这里有一个简单例子实现token认证授权访问:
后端架构token授权认证机制:spring security JSON Web Token(JWT)简例_Zhang Phil-CSDN博客
再写一个例子,该例中,假设
/api需要登录且具有有效token才能访问的接口。
/home是公开的接口,无须登录授权均可访问。
/login是登录接口,用户通过/login提交用户名和密码进行鉴权验证。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
@RestController
public class MyController {
    @Autowired
    private AuthenticationManager authenticationManager;
    //@Autowired
    //private MyUserDetailsService userDetailsService;
    @RequestMapping("/api")
    private String api(@RequestHeader Map<String, String> headers) {
        System.out.println("headers: " + headers);
        String token_header = headers.get("authorization");
        System.out.println("token: " + token_header);
        // token一般又称之为"Bearer token",以Bearer开头
        // 截取纯粹的token
        String BEARER = "Bearer ";
        String token = token_header.substring(BEARER.length());//从Bearer 之后开始截取
        String username = JwtTokenUtil.getUsername(token);
        String role = JwtTokenUtil.getUserRole(token);
        return username + " - " + role + " @api";
    }
    /**
     * 这里的MyRequest封装了用户登录需要填写上报的信息。也可以通过把用户名和密码写入http中的header实现上报。
     *
     * @param request
     * @param response
     */
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    private
    //ResponseEntity<?>
    void login(@RequestBody MyRequest request, HttpServletResponse response) {
        System.out.println("/login");
        String username = request.getUsername();
        String password = request.getPassword();
        System.out.println("用户名:" + username);
        System.out.println("密码:" + password);
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, password);
        authenticationManager.authenticate(authentication);
        //UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        String token = JwtTokenUtil.createToken(username, "ADMIN");
        //return ResponseEntity.ok(token);
        // 设置编码
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        // 请求头里返回token
        // "Bearer "前缀token
        response.setHeader("token", JwtTokenUtil.TOKEN_PREFIX + token);
        response.setContentType("text/json;charset=utf-8");
        try {
            response.getWriter().write("登录成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @RequestMapping(value = "/home")
    private String home() {
        return "home";
    }
}token工具类:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtTokenUtil {
    private static final String USER_NAME = "username";
    // Token头
    public static final String TOKEN_HEADER = "Authorization";
    // Token前缀
    public static final String TOKEN_PREFIX = "Bearer ";
    // 签名主题
    public static final String SUBJECT = "zhangphil";
    // 过期时间
    public static final long EXPIRITION = 3 * 60 * 1000;
    // 应用密钥
    public static final String APPSECRET_KEY = "zhangphil_secret";
    // 角色权限声明
    private static final String ROLE_CLAIMS = "role";
    /**
     * 生成Token
     */
    public static String createToken(String username, String role) {
        Map<String, Object> map = new HashMap<>();
        map.put(ROLE_CLAIMS, role);
        String token = Jwts
                .builder()
                .setSubject(SUBJECT)
                .setClaims(map)
                .claim(USER_NAME, username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRITION))
                .signWith(SignatureAlgorithm.HS256, APPSECRET_KEY)
                .compact();
        return token;
    }
    /**
     * 校验Token
     */
    public static Claims checkJWT(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(APPSECRET_KEY)
                    .parseClaimsJws(token)
                    .getBody();
            return claims;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return claims;
    }
    /**
     * 从Token中提取username
     */
    public static String getUsername(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get(USER_NAME).toString();
    }
    /**
     * 从Token中获取用户角色
     */
    public static String getUserRole(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get("role").toString();
    }
    /**
     * 校验Token是否过期
     */
    public static boolean isExpiration(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.getExpiration().before(new Date());
    }
}token工具类中使用的类需要在pom.xml添加引用:
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>权限异常处理:
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
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 authException) throws IOException, ServletException {
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/javascript;charset=utf-8");
        response.getWriter().print("没有访问权限");
    }
}用户登录鉴权过滤类:
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
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.ArrayList;
import java.util.Collection;
public class MyBasicAuthenticationFilter extends BasicAuthenticationFilter {
    public MyBasicAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }
    /**
     * 过滤http请求
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println(getClass().getSimpleName() + " doFilterInternal");
        String tokenHeader = request.getHeader(JwtTokenUtil.TOKEN_HEADER);
        // 请求头中没Authorization或是Authorization不以Bearer开头
        if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtil.TOKEN_PREFIX)) {
            chain.doFilter(request, response);
            System.out.println("token为空,或者token头部不是以Bearer 开头");
            return;
        }
        // 若请求头中有token
        // 设置认证信息
        SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
        super.doFilterInternal(request, response, chain);
    }
    /**
     * 从token中获取用户信息并新建一个token。
     *
     * @param tokenHeader 字符串形式的Token请求头
     * @return 带用户名和密码以及权限的Authentication
     */
    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
        // 去掉前缀,获取Token字
        String token = tokenHeader.replace(JwtTokenUtil.TOKEN_PREFIX, "");
        // 从Token中获取用户名
        String username = JwtTokenUtil.getUsername(token);
        // 从Token中解密获取用户角色
        String role = JwtTokenUtil.getUserRole(token);
        // 将[ROLE_XXX,ROLE_YYY]格式角色字符串转换为数组
        String[] roles = StringUtils.strip(role, "[]").split(", ");
        Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (String s : roles) {
            authorities.add(new SimpleGrantedAuthority(s));
        }
        if (username != null) {
            return new UsernamePasswordAuthenticationToken(username, null, authorities);
        }
        return null;
    }
}用于用户登录密码编解码处理的类:
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
/**
 * 注意,此处是明文,实际场景时候要加密
 */
@Component
public class MyPasswordEncoder extends BCryptPasswordEncoder {
    @Override
    public String encode(CharSequence rawPassword) {
        return rawPassword.toString();
    }
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        if (rawPassword == null) {
            throw new IllegalArgumentException("rawPassword为空!");
        }
        if (encodedPassword == null || encodedPassword.length() == 0) {
            throw new IllegalArgumentException("encodedPassword为空");
        }
        return encodedPassword.equals(rawPassword);
    }
}封装了用户(浏览器或客户端)发送请求的数据:
import java.io.Serializable;
public class MyRequest implements Serializable {
    private String username;
    private String password;
    //JSON Parsing
    public MyRequest() {
    }
    public MyRequest(String username, String password) {
        this.setUsername(username);
        this.setPassword(password);
    }
    public String getUsername() {
        return this.username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return this.password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}用户数据获取类,该类通常用于后端从后台的数据库根据用户名获取该用户的信息,供上层程序逻辑使用:
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.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@Service
public class MyUserDetailsService implements UserDetailsService {
    //假设已经知道用户名和密码,硬编码写死了用户名和密码
    //省去从数据库读取用户信息的过程
    public static final String USER_NAME = "zhangphil";
    public static final String USER_PASSWORD = "12345678";//正常情况密码应该加密,而不是明文。此处仅做演示。
    //数据库读写
    //@Autowired
    //private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println(username + " 加载信息");
        if (USER_NAME.equals(username)) {
            //正常情况下,这里应该从数据库中,根据传入的用户名把用户信息读出来,然后返回User
            //从数据库中查用户
            //User user = userMapper.findUserByUsername(username);
            //简单演示期间,这里省略读数据库过程。
            return new User(USER_NAME, USER_PASSWORD, new ArrayList<>());
        } else {
            throw new UsernameNotFoundException("用户不存在:" + username);
        }
    }
}这里将为登录成功的用户返回正确的token:
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
/**
 * 验证用户名和密码
 * 验证通过后,生成token返回给客户端
 */
public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private AuthenticationManager authenticationManager;
    public MyUsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }
    /**
     * 验证
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("attemptAuthentication");
        // 从输入流中获取登录信息
        // 创建token调用authenticationManager.authenticate() 后,
        // 让Spring security框架内部验证
        return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
                request.getParameter("username"),
                request.getParameter("password")));
    }
    /**
     * 验证成功,生成token返回给客户端
     */
    @Override
    public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException {
        System.out.println("successfulAuthentication ...");
        User user = (User) authResult.getPrincipal();
        // 从User中获取权限信息
        Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
        // 创建Token
        String token = JwtTokenUtil.createToken(user.getUsername(), authorities.toString());
        // 防乱码
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        // 在请求头返回token
        // 请求头带有"Bearer "前缀的token
        response.setHeader("token", JwtTokenUtil.TOKEN_PREFIX + token);
        // 防乱码
        response.setContentType("text/json;charset=utf-8");
        response.getWriter().write("登录成功");
        System.out.println("登录成功");
    }
    /**
     * 验证失败
     */
    @Override
    public void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {
        String retData;
        if (failed instanceof AccountExpiredException) {
            retData = "账号过期";
        } else if (failed instanceof BadCredentialsException) {
            retData = "密码错误";
        } else if (failed instanceof CredentialsExpiredException) {
            retData = "密码过期";
        } else if (failed instanceof DisabledException) {
            retData = "账号不可用";
        } else if (failed instanceof LockedException) {
            retData = "账号锁定";
        } else if (failed instanceof InternalAuthenticationServiceException) {
            retData = "内部授权服务异常";
        } else {
            retData = "未知异常";
        }
        response.setContentType("text/json;charset=utf-8");
        response.getWriter().write(retData);
    }
}以上各个组件、工具类的“总装车间”,组合上面的组件,形成一台能够运行的token认证鉴权系统:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyUserDetailsService userDetailsService;
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(new MyPasswordEncoder());
    }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                //login为鉴权登录,排除授权拦截
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
                // 登录拦截
                .addFilter(new MyBasicAuthenticationFilter(authenticationManagerBean()))
                // JWT鉴权拦截
                .addFilter(new MyUsernamePasswordAuthenticationFilter(authenticationManagerBean()))
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .exceptionHandling()
                // 用户访问无权限资源异常处理
                .authenticationEntryPoint(new MyAuthenticationEntryPoint());
    }
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    /**
     * 假设这一部分接口是公开开放的,不需要token即可访问。
     * 这部分客户端http请求不拦截
     * 排除。
     */
    @Override
    public void configure(WebSecurity web) {
        web.ignoring()
                .antMatchers(
                        "/login**",
                        "/home**");
    }
}最后是一个很常规、简单的application启动加载器(入口Main“函数”):
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringTokenApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringTokenApplication.class, args);
    }
}启动SpringTokenApplication,/home无需鉴权认证可以直接访问:

/login提交用户名和密码:

登录成功后,后台系统返回token:

在/api接口的header里面填入token,获取接口数据:

后端系统正确返回/api接口数据:











