• JWT和Spring Security集成


    通常情况下,把API直接暴露出去是风险很大的,

    我们一般需要对API划分出一定的权限级别,然后做一个用户的鉴权,依据鉴权结果给予用户对应的API

    (一)JWT是什么,为什么要使用它?

    互联网服务离不开用户认证。一般流程是下面这样。

    1、用户向服务器发送用户名和密码。

    2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。

    3、服务器向用户返回一个 session_id,写入用户的 Cookie。

    4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。

    5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

    这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。

    举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?

    一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。

    另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。
    (引自:阮一峰的网络日志 JSON Web Token 入门教程

    JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息

    JWT的结构

    JWT包含了使用.分隔的三部分:

    • Header 头部

    • Payload 负载

    • Signature 签名

    JWT的工作流程

    下面是一个JWT的工作流程图。模拟一下实际的流程是这样的(假设受保护的API在/protected中)

    1.用户导航到登录页,输入用户名、密码,进行登录
    2.服务器验证登录鉴权,如果用户合法,根据用户的信息和服务器的规则生成JWT Token
    3.服务器将该token以json形式返回(不一定要json形式,这里说的是一种常见的做法)
    4.用户得到token,存在localStorage、cookie或其它数据存储形式中。
    5.以后用户请求/protected中的API时,在请求的header中加入 Authorization: Bearer xxxx(token)。此处注意token之前有一个7字符长度的 Bearer
    6.服务器端对此token进行检验,如果合法就解析其中内容,根据其拥有的权限和自己的业务逻辑给出对应的响应结果。
    7.用户取得结果

    (二)SpringSecurity

    Spring Security 是为基于Spring的应用程序提供声明式安全保护的安全性框架。

    一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)

    两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,

    也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。

    系统通过校验用户名和密码来完成认证过程。

    用户授权指的是验证某个用户是否有权限执行某个操作。

    在一个系统中,不同用户所具有的权限是不同的。

    比如对一个文件来说,有的用户只能进行读取,

    而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,

    而每个角色则对应一系列的权限。

    对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。

    (三)如何利用Spring Security和JWT一起来完成API保护

    1.导入依赖

    2.配置application.properties

     spring.jackson.serialization.indent_output=true  //JSON格式化
     logging.level.org.springframework.security=info    //打印security日志记录

    3.新增 AuthorityName + Authority + 修改 Admins

    /**
     * 角色枚举类
     */
    public enum AuthorityName {
        ROLE_ADMIN,ROLE_USER
    }
    
    import java.io.Serializable;
    
    public class Authority implements Serializable {
        private Integer id;
        private AuthorityName name;
    
        public Authority() {
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public AuthorityName getName() {
            return name;
        }
    
        public void setName(AuthorityName name) {
            this.name = name;
        }
    }
    import java.io.Serializable;
    import java.util.Date;
    import java.util.List;
    
    public class Admins implements Serializable {
        private Integer aid;
        private String  aname;
        private String pwd;
        private Integer aexist;
        private Integer state;
        private Integer doid;
        private String by1;
        private Date lastPasswordResetDate;
    
        public Date getLastPasswordResetDate() {
            return lastPasswordResetDate;
        }
    
        public void setLastPasswordResetDate(Date lastPasswordResetDate) {
            this.lastPasswordResetDate = lastPasswordResetDate;
        }
    
        private List<Authority> authorities;
    
        public List<Authority> getAuthorities() {
            return authorities;
        }
    
        public void setAuthorities(List<Authority> authorities) {
            this.authorities = authorities;
        }
    
        public String getBy1() {
            return by1;
        }
    
        public void setBy1(String by1) {
            this.by1 = by1;
        }
    
        public Integer getDoid() {
            return doid;
        }
    
        public void setDoid(Integer doid) {
            this.doid = doid;
        }
    
        public Integer getState() {
            return state;
        }
    
        public void setState(Integer state) {
            this.state = state;
        }
    
        public Integer getAexist() {
            return aexist;
        }
    
        public void setAexist(Integer aexist) {
            this.aexist = aexist;
        }
    
        public Integer getAid() {
            return aid;
        }
    
        public void setAid(Integer aid) {
            this.aid = aid;
        }
    
        public String getAname() {
            return aname;
        }
    
        public void setAname(String aname) {
            this.aname = aname;
        }
    
        public String getPwd() {
            return pwd;
        }
    
        public void setPwd(String pwd) {
            this.pwd = pwd;
        }
    }

    4.创建安全服务用户

    JwtUser + JwtUserFactory + JwtUserDetailsServiceImpl + JwtAuthenticationResponse

    JwtUSer需要实现UserDetails接口,用户实体即为Spring Security所使用的用户

    /**
     * 安全服务的用户
     *  需要实现UserDetails接口,用户实体即为Spring Security所使用的用户
     */
    public class JwtUser implements UserDetails {
    
        private final   Integer id;
        private final  Integer state;
        private final String username;
        private final String password;
        private final String email;
        private final  Collection<? extends GrantedAuthority> authorities;
        private final boolean enabled;
        private final Date lastPasswordResetDate;
    
    
        public JwtUser(Integer id, Integer state, String username, String password, String email, Collection<? extends GrantedAuthority> authorities, boolean enabled, Date lastPasswordResetDate) {
            this.id = id;
            this.state = state;
            this.username = username;
            this.password = password;
            this.email = email;
            this.authorities = authorities;
            this.enabled = enabled;
            this.lastPasswordResetDate = lastPasswordResetDate;
        }
    
        public Integer getState() {
            return state;
        }
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return authorities;
        }
    
        @JsonIgnore
        @Override
        public String getPassword() {
            return password;
        }
    
        @Override
        public String getUsername() {
            return username;
        }
    
    
    
        @JsonIgnore
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @JsonIgnore
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @JsonIgnore
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return this.enabled;
        }
    
        @JsonIgnore
        public Integer getId() {
            return id;
        }
    
        public String getEmail() {
            return email;
        }
    
        @JsonIgnore
        public Date getLastPasswordResetDate() {
            return lastPasswordResetDate;
        }
    }
    public final  class JwtUserFactory {
    
        private JwtUserFactory() {
        }
    
        public static JwtUser create(Admins user){
             return new JwtUser(
                     user.getAid(),
                     user.getState(),
                     user.getAname(),
                     user.getPwd(),
                     user.getEmail(),
                     mapToGrandAuthroties(user.getAuthorities()),
                     user.getAexist()==1?true:false,
                     user.getLastPasswordResetDate()
             );
        }
    
        private static List<GrantedAuthority> mapToGrandAuthroties(List<Authority> authorities) {
            return authorities.stream()
                    .map(authority -> new SimpleGrantedAuthority(authority.getName().name()))
                    .collect(Collectors.toList());
    
    
        }
    
    }
    @Service
    public class JwtUserDetailServiceImpl implements UserDetailsService {
    
        @Autowired
        private AdminsMapper adminsMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            Admins admins = this.adminsMapper.findByUsername(username);
            if(admins==null){
                throw  new UsernameNotFoundException("No User found with UserName :"+username);
            }else{
              return JwtUserFactory.create(admins);
            }
        }
    }
    public class JwtAuthenticationResponse implements Serializable {
      private static final long serialVersionUID = 4784951536404964122L;
      private final String token;
    
      public JwtAuthenticationResponse(String token) {
        this.token = token;
      }
    
      public String getToken() {
        return this.token;
      }
    }

    配置 application.properties 支持 mybatis 映射文件 xml

    mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

    5.创建让Spring控制的安全配置类:WebSecurityConfig

    /**
     * 安全配置类
     */
    @SuppressWarnings("SpringJavaAutowiringInspection")
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
      @Autowired
      private JwtAuthenticationEntryPoint unauthorizedHandler;
    
      @Autowired
      private UserDetailsService userDetailsService;
    
    
      @Bean
      @Override
      public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
      }
    
      @Autowired
      public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                // 设置 UserDetailsService
                .userDetailsService(this.userDetailsService)
                // 使用 BCrypt 进行密码的 hash
                .passwordEncoder(passwordEncoder());
      }
    
      /**
       * 装载 BCrypt 密码编码器
       *
       * @return
       */
      @Bean
      public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
      }
    
      @Bean
      public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
        return new JwtAuthenticationTokenFilter();
      }
    
      /**
       * token请求授权
       *
       * @param httpSecurity
       * @throws Exception
       */
      @Override
      protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // we don't need CSRF because our token is invulnerable
                .csrf().disable()
                .cors().and() // 跨域
    
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
    
                // don't create session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
    
                .authorizeRequests()
                //.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
    
                // allow anonymous resource requests
                .antMatchers(
                        HttpMethod.GET,
                        "/",
                        "/*.html",
                        "/favicon.ico",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js"
                ).permitAll()
    
                // Un-secure 登录 验证码
                .antMatchers(
                        "/api/auth/**",
                        "/api/verifyCode/**",
                        "/api/global_json"
                ).permitAll()
                // secure other api
                .anyRequest().authenticated();
    
        // Custom JWT based security filter
        // 将token验证添加在密码验证前面
        httpSecurity
                .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
    
        // disable page caching
        httpSecurity
                .headers()
                .cacheControl();
      }
    }

    6.在 XxxController 加一个修饰符 @PreAuthorize("hasRole('ADMIN')") 表示这个资源只能被拥有 ADMIN 角色的用户访问

    @RequestMapping(value = "/protectedadmin", method = RequestMethod.GET)
      @PreAuthorize("hasRole('ADMIN')")
      public ResponseEntity<?> getProtectedAdmin() {
        return ResponseEntity.ok("Greetings from admin protected method!");
      }
    
      @RequestMapping(value = "/protecteduser", method = RequestMethod.GET)
      @PreAuthorize("hasRole('USER')")
      public ResponseEntity<?> getProtectedUser() {
        return ResponseEntity.ok("Greetings from user protected method!");
      }

    最后,除了 /api/auth, /api/verifycode, /api/global_json 外请求其他的路径
    访问抛异常: org.springframework.security.access.AccessDeniedException: Access is denied


    集成 JWT 和 Spring Security,完成鉴权登录,获取Token

    1.pom.xml中新增依赖 jjwt 依赖

      <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
      <dependency>
          <groupId>io.jsonwebtoken</groupId>
          <artifactId>jjwt</artifactId>
          <version>0.9.0</version>
      </dependency>
    
      <!-- https://mvnrepository.com/artifact/com.google.code.findbugs/findbugs -->
      <dependency>
          <groupId>com.google.code.findbugs</groupId>
          <artifactId>findbugs</artifactId>
          <version>3.0.1</version>
      </dependency>

    2.application.properties 配置 JWT

    3.新建一个filter: JwtAuthenticationTokenFilter :用来验证令牌的是否合法

    JwtAuthenticationEntryPoint(替代默认弹出登录页面,返回错误信息

    + JwtAuthenticationRequest (登录信息封装类)

    +JwtTokenUtil(用于生成令牌,验证等等一些操作)

    package com.wutongshu.springboot.security.filter;
    
    import com.wutongshu.springboot.security.JwtTokenUtil;
    import io.jsonwebtoken.ExpiredJwtException;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
    import org.springframework.stereotype.Component;
    import org.springframework.web.filter.OncePerRequestFilter;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * Jwt 过滤器
     */
    @Component
    public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    
      private final Log logger = LogFactory.getLog(this.getClass());
    
      @Autowired
      private UserDetailsService userDetailsService;
    
        @Value("${jwt.header}")
        private String tokenHeader;
    
        @Value("${jwt.tokenHead}")
        private String tokenHead;
    
        @Autowired
        private JwtTokenUtil jwtTokenUtil;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
             final String requestHeader  =  request.getHeader(this.tokenHeader);
             String authToken = null;
             String username = null;
             logger.info(requestHeader);
             //当前请求中包含令牌
             if(requestHeader!=null && requestHeader.startsWith(this.tokenHead)){
                 authToken = requestHeader.substring(tokenHead.length());
                 try {
                     //根据令牌信息获取用户名
                      username = jwtTokenUtil.getUsernameFromToken(authToken);
                 }catch (IllegalArgumentException e){
                     logger.error("an error occured during getting username from the token ",e);
                 }catch (ExpiredJwtException e){
                     logger.error("the token is Expried and not invalid anymore",e);
                 }
    
             }else{
                 logger.error("couldn't find Beared String,will ignore the request");
             }
             logger.info("checking Authentication with username : " + username);
             //
             if(username!=null && SecurityContextHolder.getContext().getAuthentication()==null){
                UserDetails userDetails =  userDetailsService.loadUserByUsername(username
                  );
                if(jwtTokenUtil.validateToken(authToken,userDetails)){
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    logger.info("authorication user: "+username+", setting security context");
                     SecurityContextHolder.getContext().setAuthentication(authentication);
                }
             }
             chain.doFilter(request,response);
        }
    }
    package com.wutongshu.springboot.security;
    
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.AuthenticationEntryPoint;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.Serializable;
    
    /**
     * 禁止弹出登录页面,返回错误信息
     */
    @Component
    public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
    
        private static final long serialVersionUID = -8970718410437077606L;
    
        @Override
        public void commence(HttpServletRequest request,
                             HttpServletResponse response,
                             AuthenticationException authException) throws IOException {
            // This is invoked when user tries to access a secured REST resource without supplying any credentials
            // We should just send a 401 Unauthorized response because there is no 'login page' to redirect to
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
        }
    }
    package com.wutongshu.springboot.security;
    
    import java.io.Serializable;
    
    public class JwtAuthenticationRequest implements Serializable {
    
        private String username;
        private String password;
    
        public JwtAuthenticationRequest() {
        }
    
        public JwtAuthenticationRequest(String username, String password) {
            this.username = username;
            this.password = password;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    }
    package com.wutongshu.springboot.security;
    
    import java.io.Serializable;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.function.Function;
    
    
    import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Clock;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import io.jsonwebtoken.impl.DefaultClock;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.stereotype.Component;
    
    /**
     * 工具类
     *
     */
    @Component
    public class JwtTokenUtil implements Serializable {
      private static final long serialVersionUID = -3301605591108950415L;
      @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "It's okay here")
      private Clock clock = DefaultClock.INSTANCE;
    
      //从application.properties中获取jwt.secret的值,注入到Secret中
      @Value("${jwt.secret}")
      private String secret;
    
      @Value("${jwt.expiration}")
      private Long expiration;
    
      //根据token获取username
      public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
      }
    
      public Date getIssuedAtDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getIssuedAt);
      }
    
      public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
      }
    
      public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
      }
    
      private Claims getAllClaimsFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
      }
    
      private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(clock.now());
      }
    
      private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
      }
    
      private Boolean ignoreTokenExpiration(String token) {
        // here you specify tokens, for that the expiration is ignored
        return false;
      }
    
      public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return doGenerateToken(claims, userDetails.getUsername());
      }
    
      private String doGenerateToken(Map<String, Object> claims, String subject) {
        final Date createdDate = clock.now();
        final Date expirationDate = calculateExpirationDate(createdDate);
    
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(createdDate)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
      }
    
      public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
        final Date created = getIssuedAtDateFromToken(token);
        return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
                && (!isTokenExpired(token) || ignoreTokenExpiration(token));
      }
    
      public String refreshToken(String token) {
        final Date createdDate = clock.now();
        final Date expirationDate = calculateExpirationDate(createdDate);
    
        final Claims claims = getAllClaimsFromToken(token);
        claims.setIssuedAt(createdDate);
        claims.setExpiration(expirationDate);
    
        return Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
      }
    
      public Boolean validateToken(String token, UserDetails userDetails) {
        JwtUser user = (JwtUser) userDetails;
        final String username = getUsernameFromToken(token);
        final Date created = getIssuedAtDateFromToken(token);
        return (
                username.equals(user.getUsername())
                        && !isTokenExpired(token)
                        && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())
        );
      }
    
      private Date calculateExpirationDate(Date createdDate) {
        return new Date(createdDate.getTime() + expiration * 1000);
      }
    }

    4.在 WebSecurityConfig 中注入这个filter, 并且配置到 HttpSecurity 中

    package com.wutongshu.springboot.security.config;
    
    
    import com.wutongshu.springboot.security.JwtAuthenticationEntryPoint;
    import com.wutongshu.springboot.security.filter.JwtAuthenticationTokenFilter;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpMethod;
    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.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.config.http.SessionCreationPolicy;
    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.web.authentication.UsernamePasswordAuthenticationFilter;
    
    /**
     * 安全配置类
     *
     *
     */
    @SuppressWarnings("SpringJavaAutowiringInspection")
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
      @Autowired
      private JwtAuthenticationEntryPoint unauthorizedHandler;
    
      @Autowired
      private UserDetailsService userDetailsService;
    
    
      @Bean
      @Override
      public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
      }
    
      @Autowired
      public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                // 设置 UserDetailsService
                .userDetailsService(this.userDetailsService)
                // 使用 BCrypt 进行密码的 hash
                .passwordEncoder(passwordEncoder());
      }
    
      /**
       * 装载 BCrypt 密码编码器
       *
       * @return
       */
      @Bean
      public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
      }
    
      @Bean
      public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
        return new JwtAuthenticationTokenFilter();
      }
    
      /**
       * token请求授权
       *
       * @param httpSecurity
       * @throws Exception
       */
      @Override
      protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // we don't need CSRF because our token is invulnerable
                .csrf().disable()
                .cors().and() // 跨域
    
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
    
                // don't create session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
    
                .authorizeRequests()
                //.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
    
                // allow anonymous resource requests
                .antMatchers(
                        HttpMethod.GET,
                        "/",
                        "/*.html",
                        "/favicon.ico",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js"
                ).permitAll()
    
                // Un-secure 登录 验证码
                .antMatchers(
                        "/api/auth/**",
                        "/alogin"
                ).permitAll()
                // secure other api
                .anyRequest().authenticated();
    
        // Custom JWT based security filter
        // 将token验证添加在密码验证前面
        httpSecurity
                .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
    
        // disable page caching
        httpSecurity
                .headers()
                .cacheControl();
      }
    }

    最后:

    完成鉴权(登录),注册和更新token的功能
    .AuthenticationRestController + MethodProtectedRestController + UserRestController

    package com.wutongshu.springboot.security.controller;
    
    
    
    import com.wutongshu.springboot.security.JwtAuthenticationRequest;
    import com.wutongshu.springboot.security.JwtAuthenticationResponse;
    import com.wutongshu.springboot.security.JwtTokenUtil;
    import com.wutongshu.springboot.security.JwtUser;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.http.ResponseEntity;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpServletRequest;
    
    @RestController
    @RequestMapping("/api")
    public class AuthenticationRestController {
    
      @Value("${jwt.header}")
      private String tokenHeader;
    
      @Autowired
      private AuthenticationManager authenticationManager;
    
      @Autowired
      private JwtTokenUtil jwtTokenUtil;
    
      @Autowired
      private UserDetailsService userDetailsService;
    
      @RequestMapping(value = "${jwt.route.authentication.path}", method = RequestMethod.POST)
      public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtAuthenticationRequest authenticationRequest) throws AuthenticationException {
        UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(
                authenticationRequest.getUsername(),
                authenticationRequest.getPassword()
        );
        // Perform the security
        final Authentication authentication = authenticationManager.authenticate(upToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    
        // Reload password post-security so we can generate token
        final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
        final String token = jwtTokenUtil.generateToken(userDetails);
    
        // Return the token
        return ResponseEntity.ok(new JwtAuthenticationResponse(token));
      }
    
      @RequestMapping(value = "${jwt.route.authentication.refresh}", method = RequestMethod.GET)
      public ResponseEntity<?> refreshAndGetAuthenticationToken(HttpServletRequest request) {
        String authToken = request.getHeader(tokenHeader);
        final String token = authToken.substring(7);
        String username = jwtTokenUtil.getUsernameFromToken(token);
        JwtUser user = (JwtUser) userDetailsService.loadUserByUsername(username);
    
        if (jwtTokenUtil.canTokenBeRefreshed(token, user.getLastPasswordResetDate())) {
          String refreshedToken = jwtTokenUtil.refreshToken(token);
          return ResponseEntity.ok(new JwtAuthenticationResponse(refreshedToken));
        } else {
          return ResponseEntity.badRequest().body(null);
        }
      }
    }
    package com.wutongshu.springboot.security.controller;
    
    import org.springframework.http.ResponseEntity;
    import org.springframework.security.access.prepost.PreAuthorize;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    
    @RestController
    @RequestMapping("/api")
    public class MethodProtectedRestController {
    
      /**
       * This is an example of some different kinds of granular restriction for endpoints. You can use the built-in SPEL expressions
       * in @PreAuthorize such as 'hasRole()' to determine if a user has access. Remember that the hasRole expression assumes a
       * 'ROLE_' prefix on all role names. So 'ADMIN' here is actually stored as 'ROLE_ADMIN' in database!
       **/
      @RequestMapping(value = "/protectedadmin", method = RequestMethod.GET)
      @PreAuthorize("hasRole('ADMIN')")
      public ResponseEntity<?> getProtectedAdmin() {
        return ResponseEntity.ok("Greetings from admin protected method!");
      }
    
      @RequestMapping(value = "/protecteduser", method = RequestMethod.GET)
      @PreAuthorize("hasRole('USER')")
      public ResponseEntity<?> getProtectedUser() {
        return ResponseEntity.ok("Greetings from user protected method!");
      }
    }
    package com.wutongshu.springboot.security.controller;
    
    
    import com.wutongshu.springboot.security.JwtTokenUtil;
    import com.wutongshu.springboot.security.JwtUser;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpServletRequest;
    
    
    @RestController
    @RequestMapping("/api")
    public class UserRestController {
    
      @Value("${jwt.header}")
      private String tokenHeader;
    
      @Autowired
      private JwtTokenUtil jwtTokenUtil;
    
      @Autowired
      private UserDetailsService userDetailsService;
    
      /**
       * 获取授权的用户信息
       *
       * @param request
       * @return
       */
      @RequestMapping(value = "/user", method = RequestMethod.GET)
      public JwtUser getAuthenticatedUser(HttpServletRequest request) {
        String token = request.getHeader(tokenHeader).substring(7);
        String username = jwtTokenUtil.getUsernameFromToken(token);
        JwtUser user = (JwtUser) userDetailsService.loadUserByUsername(username);
        return user;
      }
    
    }

    前台页面:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8"></meta>
      <title>JWT Spring Security Demo</title>
      <link rel="shortcut icon" href="favicon.ico" type="image/x-icon"></link>
      <!-- Latest compiled and minified CSS -->
      <link rel="stylesheet" href="../css/bootstrap.min.css"></link>
    </head>
    <body>
    <div class="container">
      <h1>JWT Spring Security Demo</h1>
    
      <div class="alert alert-danger" id="notLoggedIn">Not logged in!</div>
    
      <div class="row">
        <div class="col-md-6">
          <div class="panel panel-default" id="login">
            <div class="panel-heading">
              <h3 class="panel-title">Login</h3>
            </div>
            <div class="panel-body">
              <form id="loginForm">
                <div class="form-group">
                  <input type="text" class="form-control" id="exampleInputEmail1" placeholder="username"
                         required name="username"/>
                </div>
                <div class="form-group">
                  <input type="password" class="form-control" id="exampleInputPassword1"
                         placeholder="password" required name="password">
                </div>
                <div class="well">
                  Try one of the following logins
                  <ul>
                    <li>admin & admin</li>
                    <li>user & password</li>
                    <li>disabled & password</li>
                  </ul>
                </div>
                <button type="submit" class="btn btn-default">login</button>
              </form>
            </div>
          </div>
    
          <div id="userInfo">
            <div class="panel panel-default">
              <div class="panel-heading">
                <h3 class="panel-title">Authenticated user</h3>
              </div>
              <div class="panel-body">
                <div id="userInfoBody"></div>
                <button type="button" class="btn btn-default" id="logoutButton">logout</button>
              </div>
            </div>
          </div>
        </div>
    
        <div class="col-md-6">
          <div class="btn-group" role="group" aria-label="..." style="margin-bottom: 16px;">
            <button type="button" class="btn btn-default" id="exampleServiceBtn">call user protected service</button>
            <button type="button" class="btn btn-default" id="adminServiceBtn">call admin protected service</button>
          </div>
          <div class="panel panel-default">
            <div class="panel-heading">
              <h3 class="panel-title">Response:</h3>
            </div>
            <div class="panel-body">
              <pre id="response"></pre>
            </div>
          </div>
        </div>
      </div>
    
      <div class="row">
        <div id="loggedIn" class="col-md-6">
          <div class="panel panel-default">
            <div class="panel-heading">
              <h3 class="panel-title">Token information</h3>
            </div>
            <div class="panel-body" id="loggedInBody"></div>
          </div>
        </div>
      </div>
    </div>
    
    <div class="modal fade" tabindex="-1" role="dialog" id="loginErrorModal">
      <div class="modal-dialog">
        <div class="modal-content">
          <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
                    aria-hidden="true">&times;</span></button>
            <h4 class="modal-title">Login unsuccessful</h4>
          </div>
          <div class="modal-body"></div>
          <div class="modal-footer">
            <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
          </div>
        </div><!-- /.modal-content -->
      </div><!-- /.modal-dialog -->
    </div><!-- /.modal -->
    
    <script src="../js/jquery-3.3.1.min.js"></script>
    <!-- Latest compiled and minified JavaScript -->
    <script src="../js/bootstrap.min.js"></script>
    <script src="../js/jwt-decode.min.js"></script>
    <script src="client.js"></script>
    </body>
    </html>

    client.js:

    $(function () {
      // VARIABLES =============================================================
      var TOKEN_KEY = "jwtToken"
      var $notLoggedIn = $("#notLoggedIn");
      var $loggedIn = $("#loggedIn").hide();
      var $loggedInBody = $("#loggedInBody");
      var $response = $("#response");
      var $login = $("#login");
      var $userInfo = $("#userInfo").hide();
    
      // FUNCTIONS =============================================================
      function getJwtToken() {
        return localStorage.getItem(TOKEN_KEY);
      }
    
      function setJwtToken(token) {
        localStorage.setItem(TOKEN_KEY, token);
      }
    
      function removeJwtToken() {
        localStorage.removeItem(TOKEN_KEY);
      }
    
      function doLogin(loginData) {
        $.ajax({
          url: "http://127.0.0.1:8087/test/api/auth",
          type: "POST",
          data: JSON.stringify(loginData),
          contentType: "application/json; charset=utf-8",
          dataType: "json",
          success: function (data, textStatus, jqXHR) {
            setJwtToken(data.token);
            $login.hide();
            $notLoggedIn.hide();
            showTokenInformation();
            showUserInformation();
          },
          error: function (jqXHR, textStatus, errorThrown) {
            if (jqXHR.status === 401) {
              $('#loginErrorModal')
                  .modal("show")
                  .find(".modal-body")
                  .empty()
                  .html("<p>Spring exception:<br>" + jqXHR.responseJSON.exception + "</p>");
            } else {
              throw new Error("an unexpected error occured: " + errorThrown);
            }
          }
        });
      }
    
      function doLogout() {
        removeJwtToken();
        $login.show();
        $userInfo
            .hide()
            .find("#userInfoBody").empty();
        $loggedIn.hide();
        $loggedInBody.empty();
        $notLoggedIn.show();
      }
    
      function createAuthorizationTokenHeader() {
        var token = getJwtToken();
        if (token) {
          return {"Authorization": "Bearer " + token};
        } else {
          return {};
        }
      }
    
      function showUserInformation() {
        $.ajax({
          url: "http://127.0.0.1:8087/test/api/user",
          type: "GET",
          contentType: "application/json; charset=utf-8",
          dataType: "json",
          headers: createAuthorizationTokenHeader(),
          success: function (data, textStatus, jqXHR) {
            var $userInfoBody = $userInfo.find("#userInfoBody");
    
            $userInfoBody.append($("<div>").text("Username: " + data.username));
            $userInfoBody.append($("<div>").text("Email: " + data.email));
    
            var $authorityList = $("<ul>");
            data.authorities.forEach(function (authorityItem) {
              $authorityList.append($("<li>").text(authorityItem.authority));
            });
            var $authorities = $("<div>").text("Authorities:");
            $authorities.append($authorityList);
    
            $userInfoBody.append($authorities);
            $userInfo.show();
          }
        });
      }
    
      function showTokenInformation() {
        var jwtToken = getJwtToken();
        var decodedToken = jwt_decode(jwtToken);
    
        $loggedInBody.append($("<h4>").text("Token"));
        $loggedInBody.append($("<div>").text(jwtToken).css("word-break", "break-all"));
        $loggedInBody.append($("<h4>").text("Token claims"));
    
        var $table = $("<table>")
            .addClass("table table-striped");
        appendKeyValue($table, "sub", decodedToken.sub);
        appendKeyValue($table, "aud", decodedToken.aud);
        appendKeyValue($table, "iat", decodedToken.iat);
        appendKeyValue($table, "exp", decodedToken.exp);
    
        $loggedInBody.append($table);
    
        $loggedIn.show();
      }
    
      function appendKeyValue($table, key, value) {
        var $row = $("<tr>")
            .append($("<td>").text(key))
            .append($("<td>").text(value));
        $table.append($row);
      }
    
      function showResponse(statusCode, message) {
        $response
            .empty()
            .text("status code: " + statusCode + "
    -------------------------
    " + message);
      }
    
      // REGISTER EVENT LISTENERS =============================================================
      $("#loginForm").submit(function (event) {
        event.preventDefault();
    
        var $form = $(this);
        var formData = {
          username: $form.find('input[name="username"]').val(),
          password: $form.find('input[name="password"]').val()
        };
    
        doLogin(formData);
      });
    
      $("#logoutButton").click(doLogout);
    
      $("#exampleServiceBtn").click(function () {
        $.ajax({
          url: "http://127.0.0.1:8087/test/api/protecteduser",
          type: "GET",
          contentType: "application/json; charset=utf-8",
          headers: createAuthorizationTokenHeader(),
          success: function (data, textStatus, jqXHR) {
            showResponse(jqXHR.status, data);
          },
          error: function (jqXHR, textStatus, errorThrown) {
            showResponse(jqXHR.status, errorThrown);
          }
        });
      });
    
      $("#adminServiceBtn").click(function () {
        $.ajax({
          url: "http://127.0.0.1:8087/test/api/protectedadmin",
          type: "GET",
          contentType: "application/json; charset=utf-8",
          headers: createAuthorizationTokenHeader(),
          success: function (data, textStatus, jqXHR) {
            showResponse(jqXHR.status, data);
          },
          error: function (jqXHR, textStatus, errorThrown) {
            showResponse(jqXHR.status, errorThrown);
          }
        });
      });
    
      $loggedIn.click(function () {
        $loggedIn
            .toggleClass("text-hidden")
            .toggleClass("text-shown");
      });
    
      // INITIAL CALLS =============================================================
      if (getJwtToken()) {
        $login.hide();
        $notLoggedIn.hide();
        showTokenInformation();
        showUserInformation();
      }
    });

    效果图:

    当我们用admin的用户登录时:

    当我们用USER权限的用户登录时

     logout方法里清空token

    其他注意事项:1.每次请求都必须携带头部信息,而且必须是JSON对象类型,也就是

    createAuthorizationTokenHeader()方法

    2.WebSecurityConfig配置类里的限制路径并不需要拼接properties里的路径,也就是说方法里实际是controller层的路径

  • 相关阅读:
    从原生web组件到框架组件源码(二)
    从原生web组件到框架组件源码(一)
    拖拽滚动视图(一)
    SVG研究之路(一)下
    运算符
    编码
    格式化输出
    循环语句
    条件语句
    Python基础
  • 原文地址:https://www.cnblogs.com/wutongshu-master/p/10927870.html
Copyright © 2020-2023  润新知