springBoot+security+mybatis 完成用户权限的数据库动态办理ITeye - 凯时娱乐

springBoot+security+mybatis 完成用户权限的数据库动态办理ITeye

2019-01-10 11:29:52 | 作者: 昂雄 | 标签: 用户,权限,数据 | 浏览: 2863

/html

  好了,页面也预备好了,接着下一步。


五、根底类及查询接口的创立

  所需数据是预备好了,接下来咱们要创立一系列的数据目标,和对应的查询接口,来供 Spring Security 运用,先来创立一波数据 bean 类:SUser、SRole、SPermission,这几个别离是用户、人物、权限资源类,代码如下:

SUser.java
package com.veiking.sec.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
 * 用户名暗码信息
 * @author Veiking
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SUser {
 private int id;
 private String name;
 private String password;
 public SUser(SUser sUser) {
 this.id = sUser.getId();
 this.name = sUser.getName();
 this.password = sUser.getPassword();

SRole.java
package com.veiking.sec.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
 * 人物信息
 * @author Veiking
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SRole {
 private int id;
 private String role;
 private String describe;

SPermission.java
package com.veiking.sec.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
 * 拜访资源信息
 * @author Veiking
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SPermission {
 private int id;
 private String permission;
 private String url;
 private String describe;


  留心@Data、@NoArgsConstructor、@AllArgsConstructor这些注解,都是lombok帮忙处理set/get和全参无参结构办法的,假如不喜欢,自行替换即可。

  然后来处理查询接口,咱们这儿选用的是 mybatis 结构的办法,好了,创立几个对应的dao,代码如下:

SUserDao.java
package com.veiking.sec.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import com.veiking.sec.bean.SUser;
 * 用户信息查询
 * @author Veiking
@Mapper
public interface SUserDao {
 * 依据用户名获取用户
 * @param name
 * @return
 @Select(value = " SELECT su.* FROM s_user su WHERE su.name = #{name} ")
 public SUser findSUserByName(String name);

SRoleDao.java
package com.veiking.sec.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import com.veiking.sec.bean.SRole;
 * 人物信息查询
 * @author Veiking
@Mapper
public interface SRoleDao {
 * 依据用户ID获取人物列表
 * @param sUserId
 * @return
 @Select(value=" SELECT sr.* FROM s_role sr " + 
 " LEFT JOIN s_user_role sur ON sr.id = sur.fk_role_id " + 
 " LEFT JOIN s_user su ON sur.fk_user_id = su.id " + 
 " WHERE su.id = #{sUserId} ")
 public List SRole findSRoleListBySUserId(int sUserId);
 * 依据资源途径获取人物列表
 * @param sPermissionUrl
 * @return
 @Select(value=" SELECT sr.* FROM s_role sr " + 
 " LEFT JOIN s_role_permission srp ON sr.id = srp.fk_role_id " + 
 " LEFT JOIN s_permission sp ON srp.fk_permission_id = sp.id " + 
 " WHERE sp.url = #{sPermissionUrl} ")
 public List SRole findSRoleListBySPermissionUrl(String sPermissionUrl);

SPermissionDao.java
package com.veiking.sec.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import com.veiking.sec.bean.SPermission;
 * 资源权限信息查询
 * @author Veiking
@Mapper
public interface SPermissionDao {
 * 依据用户ID获取资源权限列表
 * @param sUserId
 * @return
 @Select(value=" SELECT * FROM s_permission sp " + 
 " LEFT JOIN s_role_permission srp ON sp.id = srp.fk_permission_id " + 
 " LEFT JOIN s_role sr ON srp.fk_role_id = sr.id " + 
 " LEFT JOIN s_user_role sur ON sr.id = sur.fk_role_id " + 
 " LEFT JOIN s_user su ON sur.fk_user_id = su.id " + 
 " WHERE su.id = #{sUserId} ")
 public List SPermission findSPermissionListBySUserId(int sUserId);
 * 依据资源途径获取资源权限列表
 * @param sPermissionUrl
 * @return
 @Select(value=" SELECT * FROM s_permission sp WHERE sp.url = #{sUserId} ")
 public List SPermission findSPermissionListBySPermissionUrl(String sPermissionUrl);


  请留心,这儿的几个Dao查询接口是运用注解的办法完结谁,当然,一般mybatis结构一般运用的办法是dao接口+xml脚本,当然个人也是习气用xml完结较为杂乱逻辑的脚本,但是在相对简略逻辑的操作上,直接用注解的办法是清新的不能再清新;两者在实践运用中是等效的,也是能够一起运用。

  这几个接口的首要作用是:通过用户名(登录名)来获取用户信息;通过用户ID、资源途径(恳求途径)来获取人物列表和权限资源列表。紧接着,本着编程习气,咱们再搞一个效劳接口,将上边几个dao的功用整合,一致对外供给数据效劳:

SecurityDataService.java
package com.veiking.sec.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.veiking.sec.bean.SPermission;
import com.veiking.sec.bean.SRole;
import com.veiking.sec.bean.SUser;
import com.veiking.sec.dao.SPermissionDao;
import com.veiking.sec.dao.SRoleDao;
import com.veiking.sec.dao.SUserDao;
 * Security 数据效劳
 * @author Veiking
@Service
public class SecurityDataService {
 @Autowired
 private SUserDao sUserDao;
 @Autowired
 private SRoleDao sRoleDao;
 @Autowired
 private SPermissionDao sPermissionDao;
 public SUser findSUserByName(String name) {
 return sUserDao.findSUserByName(name);
 public List SRole findSRoleListBySUserId(int sUserId) {
 return sRoleDao.findSRoleListBySUserId(sUserId);
 public List SRole findSRoleListBySPermissionUrl(String sPermissionUrl) {
 return sRoleDao.findSRoleListBySPermissionUrl(sPermissionUrl);
 public List SPermission findSPermissionListBySUserId(int sUserId) {
 return sPermissionDao.findSPermissionListBySUserId(sUserId);
 public List SPermission findSPermissionListBySPermissionUrl(String sPermissionUrl) {
 return sPermissionDao.findSPermissionListBySPermissionUrl(sPermissionUrl);


  这个service没有额定的操作,仅仅是传递dao的功用,OK,到此,Spring Security 需求用的数据效劳等一些预备部分,咱们都现已预备好了,下面的环节,便是要点了。

六、要点:Spring Security之用户认证

  通过一番适当罗嗦的衬托,总算迎来了正题,咱们将在接下来的环节里,叙述 Spring Security 认证有关的东西。

  首要,再次回忆,Spring Security 认证有关的重要类或接口:UserDetails、UserDetailsService、AuthenticationProvider,咱们将测验自界说封装UserDetails,经由UserDetailsService供给给AuthenticationProvider,然后和恳求音讯中获取的用户信息进行比照认证。

  首要,为了故意的来区别认证和鉴权这儿啊领域,咱们先来卖个关子,在包主途径下创立俩包:authentication、authorization,这俩单词简直是很像了,也是特意才用这两个单词,是看到有位长辈在博客中戏弄了他们,形象深入:authentication即认证,authorization即鉴权,留心字母细微的差异下在逻辑完结中不同的含义。

  好,在authentication包下来完结咱们 Spring Security 的认证,先新建一个 VUserDetails 类来完结 UserDetails(注:在此,悉数的从头完结,都将在原类或接口称号前缀加大写的V,此处仅为示例,如有仿例操作,请依据个人习气;包含之前的类或接口名,也不是很契合java引荐的命名规矩,这只是为了在称号上着重而着重,勿介意,更勿效法),代码如下:

VUserDetails.java
package com.veiking.sec.authentication;
import java.util.Collection;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import com.google.gson.Gson;
import com.veiking.sec.bean.SPermission;
import com.veiking.sec.bean.SRole;
import com.veiking.sec.bean.SUser;
 * 用户信息的封装,包含用户称号暗码及用户状况、权限等信息
 * @author Veiking
public class VUserDetails extends SUser implements UserDetails{
 private static final long serialVersionUID = 1L;
 Gson gson = new Gson();
 Logger logger = LoggerFactory.getLogger(this.getClass());
 //用户人物列表
 private List SRole sRoleList = null;
 //用户资源权限列表
 private List SPermission sPermissionList = null;
 * 留心后边的这两个参数:sRoleList、sPermissionList
 * @param sUser
 * @param sRoleList
 * @param sPermissionList
 public VUserDetails(SUser sUser, List SRole sRoleList, List SPermission sPermissionList) {
 super(sUser);
 this.sRoleList = sRoleList;
 this.sPermissionList = sPermissionList;
 * 获取用户权限列表办法
 * 能够了解成,回来了一个List String ,之后所谓的权限操控、鉴权,其实便是跟这个list里的String进行比照
 * 这儿处理了人物和资源权限两个列表,能够这么了解,
 * 人物是权限的笼统调集,是为了更便利的操控和分配权限,而真实颗粒化细节方面,仍是需求资源权限自己来做
 @Override
 public Collection ? extends GrantedAuthority getAuthorities() {
 StringBuilder authoritiesBuilder = new StringBuilder("");
 List SRole tempRoleList = this.getsRoleList();
 if (null != tempRoleList) {
 for (SRole role : tempRoleList) {
 authoritiesBuilder.append(",").append(role.getRole());
 List SPermission tempPermissionList = this.getsPermissionList();
 if (null != tempPermissionList) {
 for (SPermission permission : tempPermissionList) {
 authoritiesBuilder.append(",").append(permission.getPermission());
 String authoritiesStr = "";
 if(authoritiesBuilder.length() 0) {
 authoritiesStr = authoritiesBuilder.deleteCharAt(0).toString();
 logger.info("VUserDetails getAuthorities [authoritiesStr={} ", authoritiesStr);
 return AuthorityUtils.commaSeparatedStringToAuthorityList(authoritiesStr);
 @Override
 public String getPassword() {
 return super.getPassword();
 @Override
 public String getUsername() {
 return super.getName();
 * 判别账号是否现已过期,默许没有过期
 @Override
 public boolean isAccountNonExpired() {
 // TODO Auto-generated method stub
 return true;
 * 判别账号是否被确定,默许没有确定
 @Override
 public boolean isAccountNonLocked() {
 return true;
 * 判别信誉凭据是否过期,默许没有过期
 @Override
 public boolean isCredentialsNonExpired() {
 return true;
 * 判别账号是否可用,默许可用
 @Override
 public boolean isEnabled() {
 return true;
 public List SRole getsRoleList() {
 return sRoleList;
 public void setsRoleList(List SRole sRoleList) {
 this.sRoleList = sRoleList;
 public List SPermission getsPermissionList() {
 return sPermissionList;
 public void setsPermissionList(List SPermission sPermissionList) {
 this.sPermissionList = sPermissionList;


  留心这个VUserDetails,它承继SUser并完结了UserDetails,这个类首要功用便是封装用户信息,包含从SUser承继来的用户名暗码等特点,还有两个人物和权限的列表,留心这个 getAuthorities(),这个办法首要作业是供给一组结构界说的权限列表,能够留心下源码,这个并没有界说详细类型,咱们这儿就用String类型完结这个权限。

  这儿还要解说下,咱们在getAuthorities办法里里别离循环了两个列表来加工 Spring Security 需求权限信息,即 tempRoleList 和 tempPermissionList,能够这姿态了解,人物和权限的概念,人物自身是权限的笼统调集,是帮忙咱们开发办理的东西,真实含义的东西仍是颗粒细微的权限。添个插曲,在自己开端接触到权限规划的时分,总是傻傻的被二者的联系搞晕,加上一些实践运用的体系还乐此不疲的在权限命名上"ROLE"来"ROLE"去的,乃至一些办法命名自身也在混杂这二者(置疑可能是英语的运用习气之类的原因),导致新近的我常常常常堕入对二者的了解困惑上,当然现在明晰的许多: 在大块儿全体性的权限操控上,人物操控为主;细化到页面小块儿、按钮等级的,权限操控为主;一般再加上拜访URL的过滤鉴权,基本上一套健壮的权限操控体系是稳稳的在这儿了。

  最终留心下代码里的几个isXXX办法,这些是一些细节弥补,一般默许,也能够重写操控下逻辑;紧接着咱们新建一个 VUserDetailsService 类,来完结UserDetailsService,代码如下:

VUserDetailsService.java
package com.veiking.sec.authentication;
import java.util.List;
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 com.veiking.sec.bean.SPermission;
import com.veiking.sec.bean.SRole;
import com.veiking.sec.bean.SUser;
import com.veiking.sec.service.SecurityDataService;
 * 供给用户信息封装效劳
 * @author Veiking
@Service
public class VUserDetailsService implements UserDetailsService {
 @Autowired
 SecurityDataService securityDataService;
 * 依据用户输入的用户名回来数据源中用户信息的封装,回来一个UserDetails
 @Override
 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
 SUser sUser = securityDataService.findSUserByName(username);
 //用户人物列表
 List SRole sRoleList = securityDataService.findSRoleListBySUserId(sUser.getId());
 //用户资源权限列表
 List SPermission sPermissionList = securityDataService.findSPermissionListBySUserId(sUser.getId());
 return new VUserDetails(sUser, sRoleList, sPermissionList);


  这个类基本上没啥好说的,效劳供给者,便是一个搬运工,看这个loadUserByUsername()办法,拿到用户基本信息、人物列表和资源权限列表后,结构一个 VUserDetails 目标,OK回来。接下来是一个小要点,咱们创立一个 VAuthenticationProvider 类来完结 AuthenticationProvider,代码如下:

VAuthenticationProvider.java
package com.veiking.sec.authentication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import com.google.gson.Gson;
 * 认证供给者,校验用户,登录名暗码,以及向体系供给一个用户信息的归纳封装
 * @author Veiking
@Component
public class VAuthenticationProvider implements AuthenticationProvider {
 Gson gson = new Gson();
 Logger logger = LoggerFactory.getLogger(this.getClass());
 @Autowired
 VUserDetailsService vUserDetailsService;
 * 首要,在用户登录的时分,体系将用户输入的的用户名和暗码封装成一个Authentication目标
 * 然后,依据用户名去数据源中查找用户的数据,这个数据是封装成的VUserDetails目标
 * 接着,将两个目标进行信息比对,假如暗码正确,通过校验认证
 * 最终,将用户信息(含身份信息、细节信息、暗码、权限等)封装成一个目标,此处参阅UsernamePasswordAuthenticationToken
 * 最最终,会将这个目标交给体系SecurityContextHolder中(功用相似Session),以便后期随时取用
 @Override
 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 String username = authentication.getName();
 String password = authentication.getCredentials().toString();
 logger.info("VAuthenticationProvider authenticate login user [username={}, password={}]", username, password);
 VUserDetails vUserDetails = (VUserDetails)vUserDetailsService.loadUserByUsername(username);
 logger.info("VAuthenticationProvider authenticate vUserDetails [vUserDetails={}]", gson.toJson(vUserDetails));
 if(vUserDetails == null){
 throw new BadCredentialsException("用户没有找到");
 if (!password.equals(vUserDetails.getPassword())) {
 logger.info("VAuthenticationProvider authenticate BadCredentialsException [inputPassword={}, DBPassword={}]", password, vUserDetails.getPassword());
 throw new BadCredentialsException("暗码过错");
 //认证校验经往后,封装UsernamePasswordAuthenticationToken回来
 return new UsernamePasswordAuthenticationToken(vUserDetails, password, vUserDetails.getAuthorities());
 @Override
 public boolean supports(Class ? authentication) {
 return true;


  这个完结类的中心便是authenticate办法,一步步看,体系会将用户在登录恳求操作的时分,把输入的用户名暗码等,封装到一个Authentication目标中,咱们从这个目标里拿到用户名,通过 VUserDetailsService 获取到 VUserDetails 目标,然后拿这个目标的暗码特点,和恳求Authentication目标中获取的暗码进行比照,假如悉数OK,验证功过,然后再将这些悉数信息,全体封装成一个Authentication目标(这儿边咱们用的是UsernamePasswordAuthenticationToken),交给体系结构,后期能够随时取用。

  好了,通过上面的作业,用户认证的逻辑现已完事儿了,咱们要做拜访作业,这儿还要做些装备操作,这儿别离新建俩类,代码如下:


WebMvcConfig.java
package com.veiking.sec;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 * 拜访途径装备类
 * 能够了解成做简略拜访过滤的,转发到相应的视图页面
 * @author Veiking
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
 @Override
 public void addViewControllers(ViewControllerRegistry registry) {
 registry.addViewController("/login").setViewName("login");
 registry.addViewController("/").setViewName("index");
 registry.addViewController("/index").setViewName("index");

PageController.java
package com.veiking.sec.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
 * 恳求页面分发,留心和WebMvcConfig的比照,功用相似
 * @author Veiking
@Controller
public class PageController {
 @RequestMapping("/admin")
 public String admin(Model model, String tt) {
 return "admin";
 @RequestMapping("/hello")
 public String hello(Model model, String tt) {
 return "hello";


  WebMvcConfig 是一个简略的途径映射,功用跟在 PageController中完结的差不多,之所以多写一个PageController,是因为后边会有其他的功用演示。

  然后咱们还需创立一个 WebSecurityConfig 类来承继 WebSecurityConfigurerAdapter,代码如下:

WebSecurityConfig.java
package com.veiking.sec;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 * Security 主装备文件
 * @author Veiking
@Configuration
@EnableWebSecurity //敞开Spring Security的功用
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 * 界说不需求过滤的静态资源(等价于HttpSecurity的permitAll)
 @Override
 public void configure(WebSecurity webSecurity) throws Exception {
 webSecurity.ignoring().antMatchers("/css/**");
 * 安全策略装备
 @Override
 protected void configure(HttpSecurity httpSecurity) throws Exception {
 httpSecurity
 .authorizeRequests()
 // 关于网站部分资源需求指定鉴权
 //.antMatchers("/admin/**").hasRole("ADMIN")
 // 除上面外的悉数恳求悉数需求鉴权认证
 .anyRequest().authenticated().and()
 // 界说当需求用户登录时分,转到的登录页面
 .formLogin().loginPage("/login").defaultSuccessUrl("/index").permitAll().and()
 // 界说登出操作
 .logout().logoutSuccessUrl("/login?logout").permitAll().and()
 .csrf().disable()
 // 禁用缓存
 httpSecurity.headers().cacheControl();


  这个类是运用 Spring Security 的主装备进口,在这个装备文件中,正式启用 Spring Security 包含咱们之前所讲的悉数功用,这儿首要留心一下担任安全策略装备的 configure()办法,这个办法里能够界说登录登出等操作细节,以及一些静态资源的权限疏忽之类的,乃至也是能够直接手动配权限的。

  悉数完事儿,咱们运转 SecApplication ,开端验证之旅:



  在登录页面,输入用户名暗码:admin/admin,登录看看,随意点点跳跳,换成veiking/veiking试试,也能够输错试试,再试下登出:





  好了,这个简略的用户认证功用看来是能够了,咱们接下来看看怎样操控权限。

七、要点:Spring Security之鉴权-初试

  认证OK,回想下,是否还记得,在VAuthenticationProvider的校验环节,咱们在封装回来给体系的Authentication目标里,是供给了vUserDetails.getAuthorities()这个认证列表的,接下来看看,这个被交给体系的认证列表,是怎样表现的。

  咱们翻开 hello.html 页面,在其间的几个导航跳转的信息上,增加一个 sec:authorize="hasAuthority(XXX)" 的代码,这姿态的脚本,大约意思便是,只要名为‘XXX’的人物或许权限的用户,登录之后才能够看到,如下:

hello.html
 !DOCTYPE html 
 html xmlns="http://www.w3.org/1999/xhtml" 
 xmlns:th="http://www.thymeleaf.org"
 xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" 
 head 
 meta content="text/html;charset=UTF-8"/ 
 title HELLO /title 
 link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/ 
 style type="text/css" 
body { padding: 40px; }
 /style 
 /head 
 body 
 h1 HELLO /h1 
 br/ 你好: a sec:authentication="name" /a 
 a sec:authorize="hasAuthority(P_INDEX)" th:href="@{/index}" INDEX /a 
 a sec:authorize="hasAuthority(P_ADMIN)" th:href="@{/admin}" | ADMIN /a 
 a sec:authorize="hasAuthority(P_HELLO)" th:href="@{/hello}" | HELLO /a 
 br/ hr/ 
 form th:action="@{/logout}" method="post" sec:authorize="hasAuthority(R_ADMIN)" 
 input type="submit" value="刊出"/ 
 /form                          
			
版权声明
本文来源于网络,版权归原作者所有,其内容与观点不代表凯时娱乐立场。转载文章仅为传播更有价值的信息,如采编人员采编有误或者版权原因,请与我们联系,我们核实后立即修改或删除。

猜您喜欢的文章