zoukankan      html  css  js  c++  java
  • Spring Security和JWT实现登录授权认证

     目标

    1.Token鉴权

    2.Restful API

    3.Spring Security+JWT

    开始

    自行新建Spring Boot工程

    引入相关依赖

    复制代码
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
        <version>1.5.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.0</version>
    </dependency>
    复制代码

    User类

    非常简单的用户模型,将权限集成到了用户类中。

    复制代码
    pacage com.domain
    /** * 用户模型 * * @author hackyo * Created on 2017/12/3 11:53. */ public class User { private String id; private String username; private String password; private List<String> roles; ...... 省略get、set方法 ...... }
    复制代码

    IUserRepository类

    需实现对用户表的增删改查,此处可采用任意数据库,具体实现自行编写。

    复制代码
    package com.dao
    /** * 用户表操作接口 * * @author hackyo * Created on 2017/12/3 11:53. */ @Component public interface IUserRepository{ /** * 通过用户名查找用户 * * @param username 用户名 * @return 用户信息 */ User findByUsername(String username); }
    复制代码

    JwtUser类

    安全模块的用户模型

    复制代码
    package com.security;
    
    import com.fasterxml.jackson.annotation.JsonIgnore;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import java.util.Collection;
    
    /**
     * 安全用户模型
     *
     * @author hackyo
     * Created on 2017/12/8 9:20.
     */
    public class JwtUser implements UserDetails {
    
        private String username;
        private String password;
        private Collection<? extends GrantedAuthority> authorities;
    
        JwtUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
            this.username = username;
            this.password = password;
            this.authorities = authorities;
        }
    
        @Override
        public String getUsername() {
            return username;
        }
    
        @JsonIgnore
        @Override
        public String getPassword() {
            return password;
        }
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return authorities;
        }
    
        @JsonIgnore
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @JsonIgnore
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @JsonIgnore
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @JsonIgnore
        @Override
        public boolean isEnabled() {
            return true;
        }
    
    }
    复制代码

     JwtTokenUtil类

    Token工具类

    这里设置了密钥为aaaaaaaa,有效期为2592000秒

    复制代码
    package com.security;
    
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.stereotype.Component;
    
    import java.io.Serializable;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * JWT工具类
     *
     * @author hackyo
     * Created on 2017/12/8 9:20.
     */
    @Component
    public class JwtTokenUtil implements Serializable {
    
        /**
         * 密钥
         */
        private final String secret = "aaaaaaaa";
    
        /**
         * 从数据声明生成令牌
         *
         * @param claims 数据声明
         * @return 令牌
         */
        private String generateToken(Map<String, Object> claims) {
            Date expirationDate = new Date(System.currentTimeMillis() + 2592000L * 1000);
            return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();
        }
    
        /**
         * 从令牌中获取数据声明
         *
         * @param token 令牌
         * @return 数据声明
         */
        private Claims getClaimsFromToken(String token) {
            Claims claims;
            try {
                claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
            } catch (Exception e) {
                claims = null;
            }
            return claims;
        }
    
        /**
         * 生成令牌
         *
         * @param userDetails 用户
         * @return 令牌
         */
        public String generateToken(UserDetails userDetails) {
            Map<String, Object> claims = new HashMap<>(2);
            claims.put("sub", userDetails.getUsername());
            claims.put("created", new Date());
            return generateToken(claims);
        }
    
        /**
         * 从令牌中获取用户名
         *
         * @param token 令牌
         * @return 用户名
         */
        public String getUsernameFromToken(String token) {
            String username;
            try {
                Claims claims = getClaimsFromToken(token);
                username = claims.getSubject();
            } catch (Exception e) {
                username = null;
            }
            return username;
        }
    
        /**
         * 判断令牌是否过期
         *
         * @param token 令牌
         * @return 是否过期
         */
        public Boolean isTokenExpired(String token) {
            try {
                Claims claims = getClaimsFromToken(token);
                Date expiration = claims.getExpiration();
                return expiration.before(new Date());
            } catch (Exception e) {
                return false;
            }
        }
    
        /**
         * 刷新令牌
         *
         * @param token 原令牌
         * @return 新令牌
         */
        public String refreshToken(String token) {
            String refreshedToken;
            try {
                Claims claims = getClaimsFromToken(token);
                claims.put("created", new Date());
                refreshedToken = generateToken(claims);
            } catch (Exception e) {
                refreshedToken = null;
            }
            return refreshedToken;
        }
    
        /**
         * 验证令牌
         *
         * @param token       令牌
         * @param userDetails 用户
         * @return 是否有效
         */
        public Boolean validateToken(String token, UserDetails userDetails) {
            JwtUser user = (JwtUser) userDetails;
            String username = getUsernameFromToken(token);
            return (username.equals(user.getUsername()) && !isTokenExpired(token));
        }
    
    }
    复制代码

     JwtUserDetailsServiceImpl类

    用户验证方法类

    复制代码
    package com.security;
    
    import com.safepass.dao.IUserRepository;
    import com.safepass.domain.User;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    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.stream.Collectors;
    
    /**
     * 用户验证方法
     *
     * @author hackyo
     * Created on 2017/12/8 9:18.
     */
    @Service
    public class JwtUserDetailsServiceImpl implements UserDetailsService {
    
        private IUserRepository userRepository;
    
        @Autowired
        public JwtUserDetailsServiceImpl(IUserRepository userRepository) {
            this.userRepository = userRepository;
        }
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            User user = userRepository.findByUsername(username);
            if (user == null) {
                throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
            } else {
                return new JwtUser(user.getUsername(), user.getPassword(), user.getRoles().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
            }
        }
    
    }
    复制代码

    JwtAuthenticationTokenFilter类

    Token过滤器实现

    复制代码
    package com.security;
    
    import org.springframework.beans.factory.annotation.Autowired;
    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;
    
    /**
     * Token过滤器
     *
     * @author hackyo
     * Created on 2017/12/8 9:28.
     */
    @Component
    public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    
        private UserDetailsService userDetailsService;
        private JwtTokenUtil jwtTokenUtil;
    
        @Autowired
        public JwtAuthenticationTokenFilter(UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil) {
            this.userDetailsService = userDetailsService;
            this.jwtTokenUtil = jwtTokenUtil;
        }
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
            String authHeader = request.getHeader("Authorization");
            String tokenHead = "Bearer ";
            if (authHeader != null && authHeader.startsWith(tokenHead)) {
                String authToken = authHeader.substring(tokenHead.length());
                String username = jwtTokenUtil.getUsernameFromToken(authToken);
                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                    UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                    if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                }
            }
            chain.doFilter(request, response);
        }
    
    }
    复制代码

    EntryPointUnauthorizedHandler类

    自定义了身份验证失败的返回值

    复制代码
    package com.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;
    
    /**
     * 自定401返回值
     *
     * @author hackyo
     * Created on 2017/12/9 20:10.
     */
    @Component
    public class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint {
    
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setStatus(401);
        }
    
    }
    复制代码

    RestAccessDeniedHandler类

    自定了权限不足的返回值

    复制代码
    package com.security;
    
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.web.access.AccessDeniedHandler;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * 自定403返回值
     *
     * @author hackyo
     * Created on 2017/12/9 20:10.
     */
    @Component
    public class RestAccessDeniedHandler implements AccessDeniedHandler {
    
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) {
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setStatus(403);
        }
    
    }
    复制代码

    WebSecurityConfig类

    安全配置类

    这里设置了禁止访问所有地址,除了用于验证身份的/user/**地址

    同时密码的加密方式为BCrypt

    复制代码
    package com.security;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpMethod;
    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;
    
    /**
     * 安全模块配置
     *
     * @author hackyo
     * Created on 2017/12/8 9:15.
     */
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        private UserDetailsService userDetailsService;
        private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
        private EntryPointUnauthorizedHandler entryPointUnauthorizedHandler;
        private RestAccessDeniedHandler restAccessDeniedHandler;
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        public WebSecurityConfig(UserDetailsService userDetailsService, JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter, EntryPointUnauthorizedHandler entryPointUnauthorizedHandler, RestAccessDeniedHandler restAccessDeniedHandler) {
            this.userDetailsService = userDetailsService;
            this.jwtAuthenticationTokenFilter = jwtAuthenticationTokenFilter;
            this.entryPointUnauthorizedHandler = entryPointUnauthorizedHandler;
            this.restAccessDeniedHandler = restAccessDeniedHandler;
            this.passwordEncoder = new BCryptPasswordEncoder();
        }
    
        @Autowired
        public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
            authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder);
        }
    
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception {
            httpSecurity.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and().authorizeRequests()
                    .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                    .antMatchers("/user/**").permitAll()
                    .anyRequest().authenticated()
                    .and().headers().cacheControl();
            httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
            httpSecurity.exceptionHandling().authenticationEntryPoint(entryPointUnauthorizedHandler).accessDeniedHandler(restAccessDeniedHandler);
    
        }
    
    }
    复制代码

    IUserService类

    定义用户的基本操作

    复制代码
    package com.service;
    
    import com.domain.User;
    
    /**
     * 用户操作接口
     *
     * @author hackyo
     * Created on 2017/12/3 11:53.
     */
    public interface IUserService {
    
        /**
         * 用户登录
         *
         * @param username 用户名
         * @param password 密码
         * @return 操作结果
         */
        String login(String username, String password);
    
        /**
         * 用户注册
         *
         * @param user 用户信息
         * @return 操作结果
         */
        String register(User user);
    
        /**
         * 刷新密钥
         *
         * @param oldToken 原密钥
         * @return 新密钥
         */
        String refreshToken(String oldToken);
    
    }
    复制代码

    UserServiceImpl类

    IUserService的实现类,注册时会将用户权限设置为ROLE_USER,同时将密码使用BCrypt加密

    复制代码
    package com.service.impl;
    
    import com.dao.IUserRepository;
    import com.domain.User;
    import com.security.JwtTokenUtil;
    import com.service.IUserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.Authentication;
    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.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.stereotype.Service;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 用户操作接口实现
     *
     * @author hackyo
     * Created on 2017/12/3 11:53.
     */
    @Service
    public class UserServiceImpl implements IUserService {
    
        private AuthenticationManager authenticationManager;
        private UserDetailsService userDetailsService;
        private JwtTokenUtil jwtTokenUtil;
        private IUserRepository userRepository;
    
        @Autowired
        public UserServiceImpl(AuthenticationManager authenticationManager, UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, IUserRepository userRepository) {
            this.authenticationManager = authenticationManager;
            this.userDetailsService = userDetailsService;
            this.jwtTokenUtil = jwtTokenUtil;
            this.userRepository = userRepository;
        }
    
        @Override
        public String login(String username, String password) {
            UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);
            Authentication authentication = authenticationManager.authenticate(upToken);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            return jwtTokenUtil.generateToken(userDetails);
        }
    
        @Override
        public String register(User user) {
            String username = user.getUsername();
            if (userRepository.findByUsername(username) != null) {
                return "用户已存在";
            }
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            String rawPassword = user.getPassword();
            user.setPassword(encoder.encode(rawPassword));
            List<String> roles = new ArrayList<>();
            roles.add("ROLE_USER");
            user.setRoles(roles);
            userRepository.insert(user);
            return "success";
        }
    
        @Override
        public String refreshToken(String oldToken) {
            String token = oldToken.substring("Bearer ".length());
            if (!jwtTokenUtil.isTokenExpired(token)) {
                return jwtTokenUtil.refreshToken(token);
            }
            return "error";
        }
    
    }
    复制代码

    UserController类

    控制器,控制访问

    复制代码
    package com.controller;
    
    import com.domain.User;
    import com.service.IUserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.web.bind.annotation.*;
    
    
    /**
     * 用户管理Controller
     *
     * @author hackyo
     * Created on 2017/12/3 11:53.
     */
    @CrossOrigin
    @RestController
    @RequestMapping(value = "/user", produces = "text/html;charset=UTF-8")
    public class UserController {
    
        private IUserService userService;
    
        @Autowired
        public UserController(IUserService userService) {
            this.userService = userService;
        }
    
        /**
         * 用户登录
         *
         * @param username 用户名
         * @param password 密码
         * @return 操作结果
         * @throws AuthenticationException 错误信息
         */
        @PostMapping(value = "/login", params = {"username", "password"})
        public String getToken(String username, String password) throws AuthenticationException {
            return userService.login(username, password);
        }
    
        /**
         * 用户注册
         *
         * @param user          用户信息
         * @return 操作结果
         * @throws AuthenticationException 错误信息
         */
        @PostMapping(value = "/register")
        public String register(User user) throws AuthenticationException {
            return userService.register(user);
        }
    
        /**
         * 刷新密钥
         *
         * @param authorization 原密钥
         * @return 新密钥
         * @throws AuthenticationException 错误信息
         */
        @GetMapping(value = "/refreshToken")
        public String refreshToken(@RequestHeader String authorization) throws AuthenticationException {
            return userService.refreshToken(authorization);
        }
    
    }
    复制代码

     

    使用

    只需要在方法或类上加注解即可实现账号控制

    例如,我们想控制该方法只允许用户本人使用,#号表示方法的参数,可以在参数中加上@P('name')来指定名称,同时也可直接使用模型,如user.username等

    总之,其中可以写入任何Spring EL

    @PreAuthorize("#username == authentication.name")
        @GetMapping(value = "/getInfo")
        public String getInfo(String username) {
            return JSON.toJSONString(userService.getInfo(username));
        }

    另外也可以自定义控制注解,使用@PostFilter注解,并实现hasPermission类即可,同时需要在WebSecurityConfigurerAdapter中开启。

    测试

    运行程序后,我们使用Postman进行测试

    1.注册

    URL:http://localhost:8080/user/register

    参数:username、password

    返回success即为成功

     2.登录

    URL:http://localhost:8080/user/login

    参数:username、password

    可以看到服务器将我们的Token返回了

     3.刷新Token

    URL(GET方法):http://localhost:8080/user/refreshToken

    参数:在Header中加入登录时返回的Token,注意,需要在Token前加上“Bearer ”,最后有个空格

    Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE1MTMzMTE1NjMsInN1YiI6IjEyMyIsImNyZWF0ZWQiOjE1MTI3MDY3NjM3NjB9.baiY8QcbJgq4FQMC2piN1smbW57WjDDTiRVIL9hJeC_DcPgcyJweWqkS6g7825mPKFlByuUx7XN8nUOIszDVcw

    可以看到服务器给我们返回了新的Token,如果我们不加上Token的话,将无法访问

    参考:

      http://www.jianshu.com/p/6307c89fe3fa

      http://www.jianshu.com/p/4468a2fff879

    本文转载自

    原文作者:Hackyo

    原文链接:https://www.cnblogs.com/hackyo/p/8004928.html

  • 相关阅读:
    Linq调试实时输出信息扩展方法(摘抄)
    RSA签名和验证数据
    Vue+abp微信扫码登录
    vue学习笔记4
    icon共享网站 可以获得wpf里用的Geometry
    wpf 父控件和子控件 各自触发鼠标按下事件
    C# Timespan Tostring 时分秒格式
    新公司第二天
    C# 反转单向链表
    经纬度转数字经纬度时,如果是负数
  • 原文地址:https://www.cnblogs.com/xifengxiaoma/p/9508495.html
Copyright © 2011-2022 走看看