美文网首页Spring Security
spring Security前后端分离返回token信息

spring Security前后端分离返回token信息

作者: King斌 | 来源:发表于2020-07-24 17:33 被阅读0次

    后端项目地址:https://gitee.com/kitter/vd-mall

    前端项目地址:https://gitee.com/kitter/vd-mall-web

    添加Security配置类

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    @PropertySource("classpath:security-config.properties")
    @Slf4j
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Value("${security.ignore.resource}")
        private String[] securityIgnoreResource;
     
        @Value("${security.ignore.api}")
        private String[] securityIgnoreApi;
     
        @Value("${security.login.url}")
        private String loginApi;
     
        @Value("${security.logout.url}")
        private String logoutApi;
     
        @Value("${security.login.username.key:username}")
        private String usernameKey;
     
        @Value("${security.login.password.key:password}")
        private String passwordKey;
     
        @Autowired
        UserDetailService userDetailService;
     
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception {
            httpSecurity.csrf().disable()
                    .authorizeRequests()
                    //对于静态资源的获取允许匿名访问
                    .antMatchers(HttpMethod.GET, securityIgnoreResource).permitAll()
                    // 对登录注册要允许匿名访问;
                    .antMatchers(securityIgnoreApi).permitAll()
                    //其余请求全部需要登录后访问
                    .anyRequest().authenticated()
                    //这里配置的loginProcessingUrl为页面中对应表单的 action ,该请求为 post,并设置可匿名访问
                    .and().formLogin().loginProcessingUrl(loginApi).permitAll()
                    //这里指定的是表单中name="username"的参数作为登录用户名,name="password"的参数作为登录密码
                    .usernameParameter(usernameKey).passwordParameter(passwordKey)
                    //登录成功后的返回结果
                    .successHandler(new AuthenticationSuccessHandlerImpl())
                    //登录失败后的返回结果
                    .failureHandler(new AuthenticationFailureHandlerImpl(usernameKey))
                    //这里配置的logoutUrl为登出接口,并设置可匿名访问
                    .and().logout().logoutUrl(logoutApi).permitAll()
                    //登出后的返回结果
                    .logoutSuccessHandler(new LogoutSuccessHandlerImpl())
                    //这里配置的为当未登录访问受保护资源时,返回json
                    .and().exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPointHandler());
        }
     
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
        }
     
        @Bean
        public PasswordEncoder passwordEncoder() {
            //配置密码加密,这里声明成bean,方便注册用户时直接注入
            return new BCryptPasswordEncoder();
        }
    }
     
    

    @EnableWebSecurity:开启Security,该注解中包含@Import注解,使得WebSecurityConfiguration配置类生效
    @EnableGlobalMethodSecurity(prePostEnabled = true):开启访问权限
    @PropertySource("classpath:security-config.properties") 这里自定义的一个properties文件,通过@PropertySource注解导入后可以用@Value 读取里面的值,我这里配置的主要是SwaggerUI静态资源的忽略 以及登入登出的api地址,由于这类配置几乎不会被修改,因此这里直接独立出来一分配置文件

    security.ignore.resource=/swagger-resources/**, /v2/api-docs/**, /webjars/springfox-swagger-ui/**, /swagger-ui.html,/**/*.js
    security.ignore.api=/admin/api/v1/users/register
    security.login.url=/admin/api/v1/users/login
    security.logout.url=/admin/api/v1/users/logout
    

    上面我们提到过前后端分离我们希望所有的返回结果都以json的方式返回给前台。但是Spring Security 默认返回页面,这里我们特殊处理下。我们可以看到在 Spring Security 配置类中 有几个Handler,这里就是我们需要自己实现的地方:

    AuthenticationSuccessHandlerImpl.java 这个类定义登录成功的返回结果

    @Slf4j
    public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
        @Override
        public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
            //登录成功后获取当前登录用户
            UserDetail userDetail = (UserDetail) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            log.info("用户[{}]于[{}]登录成功!", userDetail.getUser().getUsername(), new Date());
            WriteResponse.write(httpServletResponse, new SuccessResponse());
        }
    }
    

    AuthenticationFailureHandlerImpl.java 这个类定义登录失败的返回结果,我们区分了登录失败的类型

    @Slf4j
    public class AuthenticationFailureHandlerImpl implements AuthenticationFailureHandler {
        private String usernameKey;
     
        public AuthenticationFailureHandlerImpl(String usernameKey) {
            this.usernameKey = usernameKey;
        }
     
        @Override
        public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
            GlobalResponseCode code;
     
            if (e instanceof BadCredentialsException || e instanceof UsernameNotFoundException) {
                code = GlobalResponseCode.USERNAME_OR_PASSWORD_ERROR;
            } else if (e instanceof LockedException) {
                code = GlobalResponseCode.ACCOUNT_LOCKED_ERROR;
            } else if (e instanceof CredentialsExpiredException) {
                code = GlobalResponseCode.CREDENTIALS_EXPIRED_ERROR;
            } else if (e instanceof AccountExpiredException) {
                code = GlobalResponseCode.ACCOUNT_EXPIRED_ERROR;
            } else if (e instanceof DisabledException) {
                code = GlobalResponseCode.ACCOUNT_DISABLED_ERROR;
            } else {
                code = GlobalResponseCode.LOGIN_FAILED_ERROR;
            }
            RestResponse response = new ErrorResponse(code);
            String username = httpServletRequest.getParameter(usernameKey);
            log.info("用户[{}]于[{}]登录失败,失败原因:[{}]", username, new Date(), response.getMessage());
     
            WriteResponse.write(httpServletResponse, response);
        }
    }
     
    

    LogoutSuccessHandlerImpl.java 这个类定义登出返回结果

    @Slf4j
    public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
        @Override
        public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
            if (authentication != null) {
                log.info("用户[{}]于[{}]注销成功!", ((UserDetail) authentication.getPrincipal()).getUsername(), new Date());
            }
           
            WriteResponse.write(httpServletResponse, new SuccessResponse());
        }
    }
     
    

    AuthenticationEntryPointHandler.java 这里配置的为当未登录访问受保护资源时,返回json

    public class AuthenticationEntryPointHandler implements AuthenticationEntryPoint {
       @Override
       public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
           WriteResponse.write(httpServletResponse,  new ErrorResponse(GlobalResponseCode.ACCESS_FORBIDDEN_ERROR));
       }
    }
    WriteResponse.java 将返回内容写入HttpServletResponse 
    
    class WriteResponse {
       private static final ObjectMapper mapper = new ObjectMapper();
    
       static void write(HttpServletResponse httpServletResponse, RestResponse restResponse) throws IOException {
           httpServletResponse.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
           PrintWriter out = httpServletResponse.getWriter();
           out.write(mapper.writeValueAsString(restResponse));
           out.flush();
           out.close();
       }
    }
    

    到这里我们的配置已经基本完成,当然可能有人会问用户登录时是怎么连接数据库做查询的,这里就是我们接下来要说的。

    首先创建我们的用户类 User.java

    @Data
    public class User {
        private int id;
        private String username;
        private String password;
        private String nickname;
        private String avatar;
        private int sex;
        private boolean accountNonExpired;
        private boolean accountNonLocked;
        private boolean credentialsNonExpired;
        private boolean enabled;
        private Date updateTime;
        private Date createTime;
    }
    

    创建 UserDetail.java 类 继承 UserDetails ,UserDtails 类是 Security 中对用户的抽象,包含用户的基本信息,以及角色信息

    public class UserDetail implements UserDetails {
        private User user;
        private List<String> roles;
     
        public List<String> getRoles() {
            return roles;
        }
     
        public void setRoles(List<String> roles) {
            this.roles = roles;
        }
     
        public User getUser() {
            return user;
        }
     
        public void setUser(User user) {
            this.user = user;
        }
     
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            if (roles == null || roles.isEmpty()) {
                return new ArrayList<>();
            }
     
            List<GrantedAuthority> authorities = new ArrayList<>(roles.size());
            for (String role : roles) {
                authorities.add(new SimpleGrantedAuthority(role));
            }
     
            return authorities;
        }
     
        @Override
        public String getPassword() {
            return user.getPassword();
        }
     
        @Override
        public String getUsername() {
            return user.getUsername();
        }
     
        @Override
        public boolean isAccountNonExpired() {
            return user.isAccountNonExpired();
        }
     
        @Override
        public boolean isAccountNonLocked() {
            return user.isAccountNonLocked();
        }
     
        @Override
        public boolean isCredentialsNonExpired() {
            return user.isCredentialsNonExpired();
        }
     
        @Override
        public boolean isEnabled() {
            return user.isEnabled();
        }
    }
    

    创建 UserDetailService 继承 UserDetailsService 类,根据用户名查询用户信息

    @Service
    public class UserDetailService implements UserDetailsService {
        @Autowired
        UserDao userDao;
     
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            UserDetail userDetail = userDao.getUserDetailsByUserName(username);
            if (userDetail == null) {
                throw new UsernameNotFoundException("Not found username:" + username);
            }
            return userDetail;
        }
    }
    

    配置Security 的认证管理器,在我们前面的配置类中重写 WebSecurityConfigurerAdapter 的protected void configure(AuthenticationManagerBuilder auth) 方法即可

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
    }
    

    vue 实现登录功能集成
    这里就不具体讲述Vue的详细流程了后面开单章进行讲述,这里看下效果吧,可以看到登录成功之后sessionid已经写入到了Cookies中。

    相关文章

      网友评论

        本文标题:spring Security前后端分离返回token信息

        本文链接:https://www.haomeiwen.com/subject/iekslktx.html