欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SpringBoot集成Security,前后端分离的SecurityConfig配置

程序员文章站 2024-03-20 18:33:16
...

前后端分离下保持状态是个问题,但是我这里不涉及分布式,所以用不上JWT,JWT根据项目情况来决定是否使用.

  • Authentication对象会记录用户的状态,所以不用定义一个token,从Authentication中
    getAuthorities方法获取用户的状态
  • 如果使用JWT来保持状态的话,就在拦截器上对token进行解码判断就行

先贴上Model代码:

package com.jlau.schoollocationsystem.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Created by cxr1205628673 on 2020/3/16.
 */

@Document("user")
public class OrdinaryUser extends User implements UserDetails,Serializable{
    @Id
    private String id;
    private String username;
    private String password;
    private List<Role> roles;

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> auth = new ArrayList<>();

        for (Role role:roles) {
            auth.add(new SimpleGrantedAuthority(role.getName()));
        }
        return auth;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    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;
    }
}

下面是websecurityconfig代码继承adaptor... 

 

package com.jlau.schoollocationsystem.configuration;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.jlau.schoollocationsystem.service.UserService;
import com.jlau.schoollocationsystem.utils.ResponseMsg;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by cxr1205628673 on 2020/3/16.
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    @Autowired
    private UserService userService;

    @Bean
    public PasswordEncoder getPasswordEncoder(){
        //前后端分离要注入一个PasswordEncoder来给successHandler使用
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);
        auth.userDetailsService(userService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**")
                .hasRole("ADMIN")
                .antMatchers("/user/**")
                .access("hasAnyRole('ADMIN','USER')")
                .antMatchers("/db/**")
                .hasRole("DBA")
                .antMatchers("/register")
                .permitAll()
                .anyRequest()//其他请求登录后可访问
                .authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/user/login")
                .usernameParameter("username")
                .passwordParameter("password")
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                        httpServletResponse.setContentType("application/json;charset=utf-8");
                        ObjectMapper objectMapper = new ObjectMapper();
                        Map map = new HashMap();
                        map.put("authenties",authentication.getAuthorities());
                        map.put("name",authentication.getName());
                        ResponseMsg rsp = new ResponseMsg(200,"ok",map);
                        PrintWriter out = httpServletResponse.getWriter();
                        out.write(objectMapper.writeValueAsString(rsp));
                        out.flush();
                        out.close();
                    }
                })
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                        httpServletResponse.setContentType("application/json;charset=utf-8");
                        ObjectMapper objectMapper = new ObjectMapper();
                        ResponseMsg rsp = new ResponseMsg(500,"fail",e.getMessage());
                        PrintWriter out = httpServletResponse.getWriter();
                        out.write(objectMapper.writeValueAsString(rsp));
                        out.flush();
                        out.close();
                    }
                })
                .permitAll()
                .and()
                .sessionManagement()
                .invalidSessionUrl("/session/invalid")//session过期后跳转的url
                .and()
                .logout()
                .logoutUrl("/logout")
                .clearAuthentication(true)
                .invalidateHttpSession(true)
                .addLogoutHandler(new LogoutHandler() {
                    @Override
                    public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
                        //此处可做一些清除工作
                    }
                })
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override
                    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                        ResponseMsg rsp = new ResponseMsg(200,"ok","登出成功");
                        PrintWriter out = httpServletResponse.getWriter();
                        ObjectMapper objectMapper = new ObjectMapper();
                        out.write(objectMapper.writeValueAsString(rsp));
                        out.flush();
                        out.close();
                    }
                })
                .permitAll()
                .and()
                .exceptionHandling()
                .accessDeniedHandler(new AccessDeniedHandler() {
                    @Override
                    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
                        PrintWriter out = httpServletResponse.getWriter();
                        ResponseMsg rsp = new ResponseMsg(401,"fail","权限不足");
                        ObjectMapper objectMapper = new ObjectMapper();
                        out.write(objectMapper.writeValueAsString(rsp));
                        out.flush();
                        out.close();
                    }
                })
                .and()
                .csrf()
                .disable()
                .cors();
    }
}

这里因为前后端分离,所以需要successHandler和failureHandler对用户登录成功和失败进行定制返回。

而如果权限不足需要accessDeniedHandler处理权限不足时的返回。logoutSucessHandler同理。

loginPage()方法是指定用户如果未登录,跳转到那个url上,我们就在Controller上映射上对应到处理即可(这里注意如果用户未登录,那么访问任何url都是跳转到loginPage()指定的url上,只有登录后才会有权限不足的跳转)

loginProcessingUrl()则是指定用户登录提交的的地址,usernameParameter()和passwordParameter()是指定用户登录表单中用户名和密码的name属性值(前端内容)...

permitAll()是指前一个antMatcher的地址未登录时可以访问,不受保护,antMatcher().hasRole(/**/)或者antMatcher().access(/**/)就是表示地址受保护,只有满足权限才能访问。不够权限就会用accessDeniedHandler()中的内容处理。

可能会有人问,那么Security怎么验证用户呢,一个配置类继承WebSecurityConfigurerAdapter类后重写方法:

@Autowired
private UserService userService;

@Bean
public PasswordEncoder getPasswordEncoder(){
    //前后端分离要注入一个PasswordEncoder来给successHandler使用
    return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    super.configure(auth);
    auth.userDetailsService(userService);
}

 

UserService:

package com.jlau.schoollocationsystem.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jlau.schoollocationsystem.model.OrdinaryUser;
import com.jlau.schoollocationsystem.model.Role;
import com.jlau.schoollocationsystem.model.WXUser;
import com.jlau.schoollocationsystem.repository.RoleRepsitory;
import com.jlau.schoollocationsystem.repository.UserRepository;
import com.jlau.schoollocationsystem.utils.HttpClient;
import com.jlau.schoollocationsystem.utils.ResponseMsg;
import com.jlau.schoollocationsystem.utils.WXUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Created by cxr1205628673 on 2020/3/10.
 */
@Service
public class UserService implements UserDetailsService{

    @Qualifier(value = "wxUserRepository")
    @Autowired
    private UserRepository<WXUser> userRepository;
    @Qualifier(value = "ordinaryUserRepository")
    @Autowired
    private UserRepository<OrdinaryUser> ordinaryUserUserRepository;
    @Autowired
    private RoleRepsitory roleRepsitory;
    @Autowired
    private PasswordEncoder coding;
    @Autowired
    ObjectMapper objectMapper;
    @Value("${wx.appid}")
    String appId;
    @Value("${wx.secret}")
    String secert;

    public ResponseMsg userWXLogin(String code,String encryptedData,String iv) throws Exception,NullPointerException {
        final String accessUrl = "https://api.weixin.qq.com/sns/jscode2session?" +
                "appid=" + appId + "&secret=" + secert + "&js_code=" + code + "&grant_type=authorization_code";
        String result = HttpClient.sendGet(accessUrl);
        JsonNode authResult = null;
        JsonNode userInfoResult = null;
        authResult = objectMapper.readTree(result);
        if(authResult.has("errcode")) {
            throw new Exception("授权失败" + authResult.get("errmsg").asText());
        }
        String openId = authResult.get("openid").asText();
        String sessionKey = authResult.get("session_key").asText();
        JsonNode userInfo = WXUtils.getUserInfo(encryptedData,sessionKey,iv);
        String nickName = userInfo.get("nickName").asText();
        String gender = userInfo.get("gender").asText();
        String province = userInfo.get("province").asText();
        String avatarUrl = userInfo.get("avatarUrl").asText();
        String city = userInfo.get("city").asText();
        WXUser user = new WXUser();
        user.setAvatarUrl(avatarUrl);
        user.setGender(Integer.parseInt(gender));
        user.setLanguage(province + city);
        user.setNickName(nickName);
        user.setOpenId(openId);
        user.setLoginTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        userRepository.addUser(user);
        return new ResponseMsg(200, "ok", result);
    }
    public ResponseMsg ordinaryUserLogin(String username,String password) throws Exception{
        String encrypPassword = coding.encode(password);
        OrdinaryUser ouser = ordinaryUserUserRepository.findOneUser(username);
        if(ouser != null && ouser.getUsername().equals(username) && ouser.getPassword().equals(encrypPassword)) {
            return new ResponseMsg(200,"ok","登录成功");
        }else {
            throw new Exception("用户名或密码不正确");
        }
    }
    public ResponseMsg ordinaryUserRegister(String username,String password) throws Exception{
        String encrypPassword = coding.encode(password);
        //DigestUtils.md5DigestAsHex(password.getBytes());
        OrdinaryUser ouser = ordinaryUserUserRepository.findOneUser(username);
        if(ouser != null){
            throw new Exception("用户已经存在");
        }
        OrdinaryUser user = new OrdinaryUser();
        user.setUsername(username);
        user.setPassword(encrypPassword);
        List roles = new ArrayList<>();
        Role role = roleRepsitory.findRole("ROLE_USER");
        roles.add(role);
        user.setRoles(roles);
        ordinaryUserUserRepository.addUser(user);
        return new ResponseMsg(200,"ok","注册成功");
    }

    public ResponseMsg deleteOrdinaryUser(String id) {
        ordinaryUserUserRepository.deleteUser(id);
        return new ResponseMsg(200,"ok","删除成功");
    }
    public ResponseMsg updateOrdinaryUser(OrdinaryUser user) {
        ordinaryUserUserRepository.updateUser(user);
        return new ResponseMsg(200,"ok","修改成功");
    }
    public ResponseMsg findUser(String id) {
        ordinaryUserUserRepository.findOneUser(id);
        return new ResponseMsg(200,"ok","ok");
    }
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        OrdinaryUser user = ordinaryUserUserRepository.findOneUser(username);
        if(user == null) {
            throw new UsernameNotFoundException("该账号不存在");
        }
        return user;
    }
}

该类需要继承UserDetailsService,重写loadUserByUsername(String)方法,该方法实现根据传入的用户名来查询用户。

这也是Security获取用户的地方。而判断用户名和密码是否匹配则是在更深的地方,这就需要阅读源代码了。

 

相关标签: spring boot