SpringSecurity+Jpa在前后端分离项目中的实现(含接入数据库部分)

SpringSecurity+Jpa在前后端分离项目中的实现(含接入数据库部分)

一、前言

本篇只是简单的Spring Security前后端分离的小Demo,不细讲代码,得对SpringSecurity有一定了解才能看得懂。

二、基本思路

服务端通过 JSON字符串,告诉前端用户是否登录、认证;前端根据这些提示跳转对应的登录页、认证页

三、具体实现

  • pom依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
​
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
  • 配置文件,使用Jpa:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/security_authority?useUnicode=true&characterEncoding=UTF-8
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
  • 工具类

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class AjaxResponseBody {
    ​
        //返回体中返回的状态码
        private String status;
        //返回的错误信息
        private String msg;
    }
    ​

各种Handle用于前后端交互,在Spring Security配置类中被引用,如登陆成功失败、注销的Handle类

前后端交互的Handle

原理:特定状态下返回一个特定的Json格式的数据给前端

  • 未登录

    @Component
    public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {
        @Override
        public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
    ​
            AjaxResponseBody responseBody = new AjaxResponseBody();
    ​
            responseBody.setStatus("000");
    ​
            responseBody.setMsg("无访问权限,请先登录");
    ​
            httpServletResponse.setCharacterEncoding("UTF-8");
            httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
        }
    }

     

  • 无权访问

@Component
public class AjaxAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException {
​
        AjaxResponseBody responseBody = new AjaxResponseBody();
​
        responseBody.setStatus("300");
​
        responseBody.setMsg("无权访问");
​
​
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
    }
}

 

  • 登陆失败

    @Component
    public class AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler {
        @Override
        public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws  IOException {
    ​
            AjaxResponseBody responseBody = new AjaxResponseBody();
    ​
            responseBody.setStatus("400");
    ​
            responseBody.setMsg("登录失败");
    ​
            httpServletResponse.setCharacterEncoding("UTF-8");
            httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
        }
    }
  • 登陆成功

    @Component
    public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
        @Override
        public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
    ​
            AjaxResponseBody responseBody = new AjaxResponseBody();
    ​
            responseBody.setStatus("200");
    ​
            responseBody.setMsg("登录成功");
    ​
            httpServletResponse.setCharacterEncoding("UTF-8");
            httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
        }
    }
  • 注销成功

    @Component
    public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {
        @Override
        public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
    ​
            AjaxResponseBody responseBody = new AjaxResponseBody();
    ​
            responseBody.setStatus("100");
    ​
            responseBody.setMsg("注销成功");
    ​
            httpServletResponse.setCharacterEncoding("UTF-8");
            httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
        }
    }

实体类及其业务代码

  • 用户类

@Getter
@Setter
@Entity
@Table(name = "sys_user")
public class SysUser implements Serializable, UserDetails {
​
    private static final long serialVersionUID = 1L;
​
    @Id
    @Column(name = "uid")
    private String uid;
​
    @Column(name = "username")
    private String username;
​
    @Column(name = "password")
    private String password;
​
@Getter
@Setter
@Entity
@Table(name = "sys_user")
public class SysUser implements Serializable, UserDetails {
​
    private static final long serialVersionUID = 1L;
​
    @Id
    @Column(name = "uid")
    private String uid;
​
    @Column(name = "username")
    private String username;
​
    @Column(name = "password")
    private String password;
​
    /*
    角色和用户是多对多的关系
     */
    @ManyToMany(cascade=CascadeType.REFRESH,fetch = FetchType.EAGER)
    @JoinTable(name = "sys_user_role",
    joinColumns = @JoinColumn(name = "uid",referencedColumnName = "uid"),
    inverseJoinColumns = @JoinColumn(name = "rid",referencedColumnName = "rid"))
    private List<SysRole> roles;
​
    /*
    继承了UserDetails类后会默认继承一些方法
    下面是获取用户的角色的方法,返回的角色(权限会在登陆成功后存在令牌中)
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for(SysRole role : roles){
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getRoleName());
            authorities.add(authority);
        }
        return authorities;
    }
​
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
​
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
​
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
​
    @Override
    public boolean isEnabled() {
        return true;
    }
}
    @ManyToMany(cascade=CascadeType.REFRESH,fetch = FetchType.EAGER)
    @JoinTable(name = "sys_user_role",
    joinColumns = @JoinColumn(name = "uid",referencedColumnName = "uid"),
    inverseJoinColumns = @JoinColumn(name = "rid",referencedColumnName = "rid"))
    private List<SysRole> roles;
​
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for(SysRole role : roles){
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getRoleName());
            authorities.add(authority);
        }
        return authorities;
    }
​
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
​
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
​
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
​
    @Override
    public boolean isEnabled() {
        return true;
    }
}
  • 角色类

@Getter
@Setter
@Entity
@Table(name = "sys_role")
public class SysRole implements Serializable, GrantedAuthority {
​
    private static final long serialVersionUID = 4395377670162987328L;
​
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "rid")
    private Long rid;
​
    @Column(name = "role_name")
    private String roleName;
​
    @Column(name = "role_desc")
    private String roleDesc;
​
    @ManyToMany(mappedBy = "roles",fetch = FetchType.LAZY)
    private List<SysUser> users;
    
    /*
    角色和url也是多对多的关系
     */
    @ManyToMany(cascade = CascadeType.REFRESH,fetch = FetchType.LAZY)
    @JoinTable(joinColumns = @JoinColumn(name = "rid",referencedColumnName = "rid")
    ,inverseJoinColumns = @JoinColumn(name = "mid",referencedColumnName = "mid"))
    private List<SysMenu> menus;
​
​
    @Override
    public String getAuthority() {
        return roleName;
    }
​
}
​
  • 许可类

    @Getter
    @Setter
    @Entity
    @Table(name = "sys_menu")
    public class SysMenu implements Serializable {
    ​
        private static final long serialVersionUID = -1808565992831740300L;
    ​
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "mid")
        private Long mid;
    ​
        @Column(name = "url")
        private String url;
    ​
        @ManyToMany(mappedBy = "menus",fetch = FetchType.EAGER)
        private List<SysRole> roles;
    ​
    }
  • DAO层

    • userDao

      @Repository
      public interface UserDao extends JpaRepository<SysUser,String> , JpaSpecificationExecutor<SysUser> {
      ​
          /**
           * 通过用户名获取用户实体
           * @param userName
           * @return
           */
          SysUser findUserByUsername(String userName);
      }
    • RoleDao

      @Repository
      public interface RoleDao extends JpaRepository<SysRole,Long>, JpaSpecificationExecutor<SysRole> {
      ​
          /**
           * 添加一个角色
           * @param uid
           * @param rid
           * @return
           */
          @Modifying
          @Query(value = "insert into sys_user_role values(?1,?2)",nativeQuery = true)
          int grant(String uid,Long rid);
      ​
          /**
           * 删除一个角色
           * @param uid
           * @param rid
           * @return
           */
          @Modifying
          @Query(value = "delete from sys_user_role where uid = ?1 and rid = ?2",nativeQuery = true)
          int revoke(String uid,Long rid);
      ​
          /**
           * 获取该角色下的url
           * @param Url
           * @return
           */
          @Query(value = "select r.role_name from sys_role r,sys_role_menus rm,sys_menu m where rm.rid = r.rid and rm.mid = m.mid and m.url =?1",nativeQuery = true)
          List<String> selectRoleNameByUrl(String Url);
      }
  • Service层

    • UserServiceImpl

      @Service
      public class UserServiceImpl implements UserService {
      ​
          @Autowired
          UserDao userDao;
      ​
      ​
          @Override
          public void saveUser(SysUser user) {
              String password = user.getPassword();
              //新建用户时加密密码
              String encodePassword = new BCryptPasswordEncoder().encode(password);
              user.setPassword(encodePassword);
              //分配id
              user.setUid(IdUtil.simpleUUID());
              userDao.save(user);
          }
      ​
          @Override
          public UserDetails loadUserByUserName(String username) {
              return userDao.findUserByUsername(username);
          }
      }
    • RoleServiceImpl

      @Transactional
      @Service
      public class RoleServiceImpl implements RoleService {
      ​
          @Autowired
          RoleDao roleDao;
      ​
          @Override
          public boolean grant(String uid, Long rid) {
              if(roleDao.grant(uid,rid) > 0){
                  return true;
              }
              return false;
          }
      ​
          @Override
          public boolean revoke(String uid, Long rid) {
              if(roleDao.revoke(uid,rid) > 0){
                  return true;
              }
              return false;
          }
      ​
          @Override
          public void saveRole(SysRole role) {
              roleDao.save(role);
          }
      ​
          @Override
          public List<String> getRoleNameByUrl(String url) {
              return roleDao.selectRoleNameByUrl(url);
          }
      }
  • Controller层

    • UserController

      @RestController
      @RequestMapping("/user")
      public class UserController {
      ​
          @Autowired
          UserService userService;
      ​
          @GetMapping({"/","/index"})
          public String index(){
              return "index";
          }
      ​
          @GetMapping("/root")
          public String toRoot(){
              return "root";
          }
      ​
          @GetMapping("/manager")
          public String toManager(){
              return "manager";
          }
      ​
          @PostMapping("/register")
          public String register(SysUser user){
              userService.saveUser(user);
              return "register";
          }
      ​
      }
    • RoleController

      @RestController
      @RequestMapping("/role")
      public class RoleController {
      ​
          @Autowired
          RoleService roleService;
      ​
          @PostMapping
          public String save(SysRole role){
              roleService.saveRole(role);
              return "saveRole";
          }
      ​
          /**
           * 给角色授权
           * @param uid
           * @param rid
           * @return
           */
          @PutMapping("/grant")
          public String grant(String uid,Long rid){
              if(roleService.grant(uid, rid)){
                  return "success";
              }else {
                  return "failure";
              }
          }
      ​
          /**
           * 撤销角色权限
           * @param uid
           * @param rid
           * @return
           */
          @DeleteMapping("/revoke")
          public String revoke(String uid,Long rid){
      ​
              if(roleService.revoke(uid, rid)){
                  return "success";
              }else {
                  return "failure";
              }
      ​
          }

Jpa会根据实体之间的关系自动创建数据库

 

认证的Provider

@Component
public class SelfAuthenticationProvider implements AuthenticationProvider {
​
    @Autowired
    UserService userService;
​
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
​
        //获取用户登录输入的信息
        String userName = (String) authentication.getPrincipal();
        String password = (String) authentication.getCredentials();
​
        //用注册用户时相同的加密方式加密
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
​
        //根据用户名获取数据库中的用户
        UserDetails userInfo = userService.loadUserByUserName(userName);
​
        //进行密码比对
        if (!encoder.matches(password,userInfo.getPassword())) {
            throw new BadCredentialsException("用户名密码不正确,请重新登陆!");
        }
​
        //比对成功返回token令牌
        return new UsernamePasswordAuthenticationToken(userName, password, userInfo.getAuthorities());
    }
​
     /**
     * 如果该AuthenticationProvider支持传入的Authentication对象,则返回true
     * @param authentication
     * @return
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return true;
    }
}

Security的配置类

拦截和认证执行的地方

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
​
    //  未登陆时返回 JSON 格式的数据给前端(否则为 html)
    @Autowired
    AjaxAuthenticationEntryPoint authenticationEntryPoint;
​
    // 登录成功返回的 JSON 格式数据给前端(否则为 html)
    @Autowired
    AjaxAuthenticationSuccessHandler authenticationSuccessHandler;
​
    //  登录失败返回的 JSON 格式数据给前端(否则为 html)
    @Autowired
    AjaxAuthenticationFailureHandler authenticationFailureHandler;
​
    // 注销成功返回的 JSON 格式数据给前端(否则为 登录时的 html)
    @Autowired
    AjaxLogoutSuccessHandler logoutSuccessHandler;
​
    // 无权访问返回的 JSON 格式数据给前端(否则为 403 html 页面)
    @Autowired
    AjaxAccessDeniedHandler accessDeniedHandler;
​
    // 自定义安全认证
    @Autowired
    SelfAuthenticationProvider provider;
​
    //安全认证
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(provider);
    }
​
    //授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
​
        //关闭跨域拦截
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/user/","/user/register").permitAll()
                /*.antMatchers("/user/manager").hasAnyRole("MANAGER","ROOT")
                将授权操作分配root权限
                .antMatchers("/user/root","/role/grant").hasRole("ROOT")*/
                //根据角色动态分配路由
                .withObjectPostProcessor(new DefinedObjectPostProcessor())
                //配置决策方式
                .accessDecisionManager(accessDecisionManager())
​
​
                .and()
                .exceptionHandling()
                //未登录拦截
                .authenticationEntryPoint(authenticationEntryPoint)
                //无权访问拦截
                .accessDeniedHandler(accessDeniedHandler)
​
                //登录
                .and()
                .formLogin()
                .usernameParameter("username")
                .passwordParameter("password")
                .loginProcessingUrl("/login")
                .successHandler(authenticationSuccessHandler)
                .failureHandler(authenticationFailureHandler)
                .permitAll()
​
                //注销
                .and()
                .logout()
                .logoutSuccessHandler(logoutSuccessHandler);
    }
​
​
    /**
     * AffirmativeBased – 任何一个AccessDecisionVoter返回同意则允许访问
     * ConsensusBased – 同意投票多于拒绝投票(忽略弃权回答)则允许访问
     * UnanimousBased – 每个投票者选择弃权或同意则允许访问
     *
     * 决策管理
     */
    private AccessDecisionManager accessDecisionManager() {
        List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<>();
        decisionVoters.add(new WebExpressionVoter());
        decisionVoters.add(new AuthenticatedVoter());
    /*    decisionVoters.add(new RoleVoter());*/
        /* 路由权限管理 */
        decisionVoters.add(new UrlRoleAuthHandler());
        return new UnanimousBased(decisionVoters);
    }
​
    @Autowired
    private UrlRolesFilterHandler urlRolesFilterHandler;
​
​
    class DefinedObjectPostProcessor implements ObjectPostProcessor<FilterSecurityInterceptor> {
        @Override
        public <O extends FilterSecurityInterceptor> O postProcess(O object) {
            object.setSecurityMetadataSource(urlRolesFilterHandler);
            return object;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值