• SpringBoot 整合Spring Security + JWT 实现前后端分离项目的认证授权


     以下是伪代码,要根据自己的业务自行修改

    引入依赖

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>

      还用了jwt、redis、fastjson 等 如果添加了就不用添加

      <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
            </dependency>
    
    
            <!-- 阿里JSON解析器 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.80</version>
            </dependency>
     <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>

    JAVA结合 JSON Web Token(JWT) 工具类参考:https://www.cnblogs.com/pxblog/p/12954756.html

     SpringBoot 整合Redis 参考:https://www.cnblogs.com/pxblog/p/12980634.html

     Spring Boot全局异常处理参考:https://www.cnblogs.com/pxblog/p/14307697.html

    SercurityConfig.java

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.authentication.AuthenticationManager;
    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.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    import org.springframework.security.web.authentication.logout.LogoutFilter;
    import org.springframework.web.filter.CorsFilter;
    
    import javax.annotation.Resource;
    
    /**
     * @author yvioo。
     */
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
    public class SercurityConfig extends WebSecurityConfigurerAdapter {
    
    
        /**
         * token认证过滤器
         */
        @Autowired
        private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    
    
        /**
         * 认证失败处理类
         */
        @Autowired
        private AuthenticationEntryPointImpl unauthorizedHandler;
    
        /**
         * 退出处理类
         */
        @Autowired
        private LogoutSuccessHandlerImpl logoutSuccessHandler;
    
        /**
         * 跨域过滤器
         */
        @Resource
        private CorsFilter corsFilter;
    
        /**
         * 密码加密方式
         * 创建BCryptPasswordEncoder注入容器
         *
         * @return
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            //如果不加密,使用 NoOpPasswordEncoder.getInstance();
            return new BCryptPasswordEncoder();
        }
    
    
        /**
         * 解决 无法直接注入 AuthenticationManager
         *
         * @return
         * @throws Exception
         */
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        /**
         * anyRequest          |   匹配所有请求路径
         * access              |   SpringEl表达式结果为true时可以访问
         * anonymous           |   匿名可以访问
         * denyAll             |   用户不能访问
         * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
         * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
         * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
         * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
         * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
         * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
         * permitAll           |   用户可以任意访问
         * rememberMe          |   允许通过remember-me登录的用户访问
         * authenticated       |   用户登录后可访问
         */
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception {
            httpSecurity
                    // CSRF禁用,因为不使用session
                    .csrf().disable()
                    // 认证失败处理类 如果没有可以不加
                    .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                    // 基于token,所以不需要session
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                    // 过滤请求
                    .authorizeRequests()
                    // 对于登录login 注册register 验证码captchaImage 允许匿名访问
                    .antMatchers("/login", "/register", "/captchaImage").anonymous()
                    .antMatchers(
                            HttpMethod.GET,
                            "/",
                            "/*.html",
                            "/**/*.html",
                            "/**/*.css",
                            "/**/*.js",
                            "/profile/**"
                    ).permitAll()
                    .antMatchers("/swagger-ui.html").anonymous()
                    .antMatchers("/swagger-resources/**").anonymous()
                    .antMatchers("/webjars/**").anonymous()
                    .antMatchers("/*/api-docs").anonymous()
                    .antMatchers("/druid/**").anonymous()
                    // 除上面外的所有请求全部需要鉴权认证
                    .anyRequest().authenticated()
                    .and()
                    .headers().frameOptions().disable();
            httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
            // 添加JWT filter
            httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
            // 添加CORS filter
            httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
            httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
        }
    
    
    }

    User.java

    import lombok.Data;
    
    /**
     * 用户对象
     * @author*/
    @Data
    public class User {
    
    
        private Long userId;
    
        private String userName;
    
        private String password;
    
        private boolean admin;
    }

    LoginUser.java

    import com.alibaba.fastjson.annotation.JSONField;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import java.util.Collection;
    import java.util.Set;
    
    /**
     * 登录用户身份权限
     *
     * @author
     */
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    public class LoginUser implements UserDetails {
        private static final long serialVersionUID = 1L;
    
        /**
         * 用户ID
         */
        private Long userId;
    
    
        /**
         * 用户唯一标识
         */
        private String token;
    
        /**
         * 登录时间
         */
        private Long loginTime;
    
        /**
         * 过期时间
         */
        private Long expireTime;
        /**
         * 权限列表
         */
        private Set<String> permissions;
    
        /**
         * 用户信息
         */
        private User user;
    
        public LoginUser(User user, Set<String> permissions) {
            this.user = user;
            this.permissions = permissions;
        }
    
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return null;
        }
    
    
        @JSONField(serialize = false)
        @Override
        public String getPassword() {
            return user.getPassword();
        }
    
        @Override
        public String getUsername() {
            return user.getUserName();
        }
    
        /**
         * 账户是否未过期,过期无法验证
         */
        @JSONField(serialize = false)
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        /**
         * 指定用户是否解锁,锁定的用户无法进行身份验证
         *
         * @return
         */
        @JSONField(serialize = false)
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        /**
         * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
         *
         * @return
         */
        @JSONField(serialize = false)
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        /**
         * 是否可用 ,禁用的用户不能身份验证
         *
         * @return
         */
        @JSONField(serialize = false)
        @Override
        public boolean isEnabled() {
            return true;
        }
    }

    LoginController.java

    import com.example.security.entity.R;
    import com.example.security.service.UserService;
    import com.example.security.entity.User;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.access.prepost.PreAuthorize;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     *  登录控制器
     * @author*/
    @RestController
    public class LoginController {
    
    
        @Autowired
        private UserService userService;
    
    
        /**
         *  R类参考:https://www.cnblogs.com/pxblog/p/13792038.html
         * @param user
         * @return
         */
        @PostMapping(value = "/login")
        public R login(@RequestBody User user){
            //返回生成的token
            return R.success(userService.login(user));
        }
    
    
        @GetMapping(value = "/hello")
        @PreAuthorize("hasAuthority('show:hello')")
        public String hello(){
            return "hello";
        }
    }

    UserService.java

    import com.example.security.entity.LoginUser;
    import com.example.security.entity.User;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.authentication.BadCredentialsException;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.Authentication;
    import org.springframework.stereotype.Service;
    
    /**
     * @author*/
    @Service
    public class UserService {
    
    
        @Autowired
        private TokenService tokenService;
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
    
        public String login(User user) {
    
            // 用户验证
            Authentication authentication = null;
            try {
                // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
                authentication = authenticationManager
                        .authenticate(new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword()));
            } catch (Exception e) {
                if (e instanceof BadCredentialsException) {
                    //这里要自行添加全局异常处理,参考:https://www.cnblogs.com/pxblog/p/14307697.html
                    throw new RuntimeException("用户名和密码不匹配");
                } else {
                    throw new RuntimeException(e.getMessage());
                }
            }
    
            //没有异常 表示登录成功
            LoginUser loginUser = (LoginUser) authentication.getPrincipal();
    
    
            return tokenService.createToken(loginUser);
        }
    
    
        public User selectUserByUserName(String username) {
            /**
             * 查询用户
             */
            return null;
        }
    }

    UserDetailsServiceImpl.java

    import com.example.security.entity.LoginUser;
    import com.example.security.entity.User;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    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.Arrays;
    import java.util.HashSet;
    import java.util.Set;
    
    /**
     * @author*/
    @Service
    @Slf4j
    public class UserDetailsServiceImpl implements UserDetailsService {
    
    
        @Autowired
        private UserService userService;
    
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
            /**
             * 这里根据用户名去数据库查询用户是否存在,这里按照自己的逻辑来写即可
             *  可能还有禁用,或者已经被逻辑删除 的判断
             */
            User user = userService.selectUserByUserName(username);
            if (user==null) {
                log.info("登录用户:{} 不存在.", username);
                throw new RuntimeException("登录用户:" + username + " 不存在");
            }
    
            /**
             * 判断成功后,封装成一个对象返回
             */
    
            return new LoginUser(user, getMenuPermission(user));
        }
    
    
        public Set<String> getMenuPermission(User user) {
            Set<String> perms = new HashSet<String>();
    
            if (user.isAdmin()) {
                /**
                 * 管理员拥有所有权限
                 */
                perms.add("*:*:*");
            } else {
                /**
                 * 根据用户ID去数据库查询菜单权限,封装成一个list返回,这里只是举例
                 * 注意:需要把权限换成以下这种方式 这里权限和 @PreAuthorize 上的对应
                 */
                perms.addAll(Arrays.asList("show:hello"));
            }
            return perms;
        }
    
    
    }

    TokenService.java

    import com.example.security.config.JWTUtils;
    import com.example.security.config.RedisService;
    import com.example.security.entity.LoginUser;
    import io.jsonwebtoken.Claims;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.concurrent.TimeUnit;
    
    /**
     * token验证处理
     *
     * @author*/
    @Component
    public class TokenService {
    
    
        /**
         *  令牌自定义标识
         *   一般是:token
         */
        @Value("${token.header}")
        private String header;
    
        /**
         * 令牌有效期
         * (默认30分钟)
         */
        @Value("${token.expireTime}")
        private int expireTime;
    
        /**
         * 令牌前缀
         */
        public static final String LOGIN_USER_KEY = "login_user_key";
    
        /**
         * 登录用户 redis key
         */
        public static final String LOGIN_TOKEN_KEY = "login_tokens:";
    
        protected static final long MILLIS_SECOND = 1000;
    
        protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
    
        private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
    
        /**
         * 整合redis参考:https://www.cnblogs.com/pxblog/p/12980634.html
         */
        @Autowired
        private RedisService redisService;
    
        /**
         * 获取用户身份信息
         *
         * @return 用户信息
         */
        public LoginUser getLoginUser(HttpServletRequest request) {
            // 获取请求携带的令牌
            String token = getToken(request);
            if (StringUtils.isNotEmpty(token)) {
                try {
                    Claims claims = JWTUtils.parseJWT(token);
                    // 解析对应的权限以及用户信息
                    String uuid = (String) claims.get(LOGIN_USER_KEY);
                    String userKey = getTokenKey(uuid);
                    LoginUser user = redisService.getCacheObject(userKey);
                    return user;
                } catch (Exception e) {
                }
            }
            return null;
        }
    
        /**
         * 设置用户身份信息
         *  修改用户资料,或者修改用户角色的时候修改同步缓存数据
         */
        public void setLoginUser(LoginUser loginUser) {
            if (loginUser != null && StringUtils.isNotEmpty(loginUser.getToken())) {
                refreshToken(loginUser);
            }
        }
    
        /**
         * 删除用户身份信息
         */
        public void delLoginUser(String token) {
            if (StringUtils.isNotEmpty(token)) {
                String userKey = getTokenKey(token);
                redisService.del(userKey);
            }
        }
    
        /**
         * 创建令牌
         * JWTUtils 参考:https://www.cnblogs.com/pxblog/p/12954756.html
         * @param loginUser 用户信息
         * @return 令牌
         */
        public String createToken(LoginUser loginUser) {
            String token = JWTUtils.createJWT(loginUser.getUserId().toString(),"yvioo",loginUser.getUsername(),null,loginUser.getExpireTime());
            loginUser.setToken(token);
            refreshToken(loginUser);
    
            return token;
        }
    
        /**
         * 验证令牌有效期,相差不足20分钟,自动刷新缓存
         *
         * @param loginUser
         * @return 令牌
         */
        public void verifyToken(LoginUser loginUser) {
            long expireTime = loginUser.getExpireTime();
            long currentTime = System.currentTimeMillis();
            if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
                refreshToken(loginUser);
            }
        }
    
        /**
         * 刷新令牌有效期
         *
         * @param loginUser 登录信息
         */
        public void refreshToken(LoginUser loginUser) {
            loginUser.setLoginTime(System.currentTimeMillis());
            loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
            // 根据uuid将loginUser缓存
            String userKey = getTokenKey(loginUser.getToken());
            redisService.set(userKey, loginUser, expireTime, TimeUnit.MINUTES);
        }
    
    
        /**
         * 获取请求token
         *
         * @param request
         * @return token
         */
        private String getToken(HttpServletRequest request) {
            String token = request.getHeader(header);
            return token;
        }
    
        private String getTokenKey(String uuid) {
            return LOGIN_TOKEN_KEY + uuid;
        }
    }

    JwtAuthenticationTokenFilter.java

    import com.example.security.entity.LoginUser;
    import com.example.security.service.TokenService;
    import org.apache.commons.lang3.ObjectUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.context.SecurityContextHolder;
    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过滤器 验证token有效性
     *
     * @author*/
    @Component
    public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    
        @Autowired
        private TokenService tokenService;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
                throws ServletException, IOException {
    
            LoginUser loginUser = tokenService.getLoginUser(request);
            if (!ObjectUtils.isEmpty(loginUser)){
                //token不为空 验证token
                tokenService.verifyToken(loginUser);
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    
            }
    
            chain.doFilter(request, response);
        }
    }

    LogoutSuccessHandlerImpl.java

    import com.alibaba.fastjson.JSON;
    import com.example.security.entity.LoginUser;
    import com.example.security.entity.R;
    import com.example.security.service.TokenService;
    import org.apache.commons.lang3.ObjectUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * 自定义退出处理类 返回成功
     *
     * @author*/
    @Configuration
    public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
        @Autowired
        private TokenService tokenService;
    
        /**
         * 退出处理
         *
         * @return
         */
        @Override
        public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
            LoginUser loginUser = tokenService.getLoginUser(request);
            if (!ObjectUtils.isEmpty(loginUser)) {
                // 删除用户缓存记录
                tokenService.delLoginUser(loginUser.getToken());
            }
            /**
             * 这里可以自己封装返回前端对象,根据自己的来
             */
            renderString(response, JSON.toJSONString(R.success("退出成功")));
        }
    
    
        /**
         * 将字符串渲染到客户端
         *
         * @param response 渲染对象
         * @param string   待渲染的字符串
         */
        public static void renderString(HttpServletResponse response, String string) {
            try {
                response.setStatus(200);
                response.setContentType("application/json");
                response.setCharacterEncoding("utf-8");
                response.getWriter().print(string);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    AuthenticationEntryPointImpl.java

    import lombok.extern.slf4j.Slf4j;
    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.Serializable;
    
    /**
     * 认证失败处理类
     *
     * @author .
     */
    @Component
    @Slf4j
    public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable {
    
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
            log.error("认证失败了");
    
        }
    }

    以及跨域处理

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
    import org.springframework.web.filter.CorsFilter;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    /**
     *
     * @author .
     */
    @Configuration
    public class ResourcesConfig implements WebMvcConfigurer {
    
    
        /**
         * 跨域配置
         */
        @Bean
        public CorsFilter corsFilter() {
            CorsConfiguration config = new CorsConfiguration();
            config.setAllowCredentials(true);
            // 设置访问源地址
            config.addAllowedOriginPattern("*");
            // 设置访问源请求头
            config.addAllowedHeader("*");
            // 设置访问源请求方法
            config.addAllowedMethod("*");
            // 有效期 1800秒
            config.setMaxAge(1800L);
            // 添加映射路径,拦截一切请求
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            source.registerCorsConfiguration("/**", config);
            // 返回新的CorsFilter
            return new CorsFilter(source);
        }
    }
  • 相关阅读:
    网络游戏
    嘎嘎
    Failed to install *.apk on device 'emulator-5554': timeout
    安卓开发真机遇到Failed to install Spaceassault.apk on device 'HT1CKV205198': timeout 测试机没有问题
    java匿名内部类
    TextView tv01=(TextView)this.findViewById(R.id.TextView01); tv01.setText("设置文字背景色");
    android 项目中出现红色感叹号的解决方法
    使用block来解决实现switch解决字符串
    oc中的block
    不可变数组或者可变数组进行排序
  • 原文地址:https://www.cnblogs.com/pxblog/p/16180791.html
Copyright © 2020-2023  润新知