srpingboot springsecurity vue rabc登录验证

利用springsecurity 自带的SEESION做的登陆,JWT TOKEN也可以实现,但是需要配置过期处理,至于怎么做是仁者见仁智者见智的事。

首先建立用户 角色 权限,老司机们都知道不再详述:

用户对象:

/**
 * 用户对象
 *
 */
@Entity
@Table(name="USER")
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class User extends IdEntity {  

	private static final long serialVersionUID = -6998691082059319752L;
	

	protected String userName;//用户名
	protected String empName;//员工英文名
	protected String password;//密码
	protected Date createDate;//创建时间
	protected Date lastLoginDate;//最后登录时间
	protected String phone;//手机号
	
	protected Set<Role> roles = new HashSet<Role>();//拥有权限

	public User() {
		super();
	}

	public User(String userName, String empName, String password, Date createDate, Date lastLoginDate, String phone) {
		super();
		this.userName = userName;
		this.empName = empName;
		this.password = password;
		this.createDate = createDate;
		this.lastLoginDate = lastLoginDate;
		this.phone = phone;
	}

	@Column(name="USER_NAME")
	public String getUserName() {
		return userName;
	}

	@Column(name="EMP_NAME")
	public String getEmpName() {
		return empName;
	}

	@Column(name="PASSWORD")
	public String getPassword() {
		return password;
	}

	@Column(name="CREATE_TIME")
	public Date getCreateDate() {
		return createDate;
	}

	@Column(name="LAST_LOGIN_TIME")
	public Date getLastLoginDate() {
		return lastLoginDate;
	}

	@ManyToMany(fetch=FetchType.LAZY, targetEntity=Role.class)
	@JoinTable(name="USER_ROLE", 
			joinColumns = {@JoinColumn(name="USER_ID")},
			inverseJoinColumns = {@JoinColumn(name="ROLE_ID")})
	public Set<Role> getRoles() {
		return roles;
	}

	@Column(name="PHONE")
	public String getPhone() {
		return phone;
	}

	public void setPhone(String phone) {
		this.phone = phone;
	}

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

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public void setEmpName(String empName) {
		this.empName = empName;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public void setCreateDate(Date createDate) {
		this.createDate = createDate;
	}

	public void setLastLoginDate(Date lastLoginDate) {
		this.lastLoginDate = lastLoginDate;
	}

	@Override
	public String toString() {
		return "User [userName=" + userName + ", empName=" + empName + ", password=" + password + ", createDate="
				+ createDate + ", lastLoginDate=" + lastLoginDate + ", phone=" + phone + ", roles=" + roles + "]";
	}
	
}

角色对象:

/**
 * 角色对象
 */
@Entity
@Table(name="ROLE")
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class Role extends IdEntity {  

	private static final long serialVersionUID = -6998691082059319752L;
	
	protected String name;//角色名称
	protected String description;//角色描述
	protected Set<Permission> permissions = new HashSet<Permission>();//拥有权限
	protected Set<User> users = new HashSet<User>();//拥有角色的用户

	@Column(name="NAME")
	public String getName() {
		return name;
	}

	@Column(name="DESCRIPTION")
	public String getDescription() {
		return description;
	}
	
	@ManyToMany(fetch=FetchType.LAZY, targetEntity=Permission.class)
	@JoinTable(name="ROLE_PERMISSION", 
			joinColumns = {@JoinColumn(name="ROLE_ID")},
			inverseJoinColumns = {@JoinColumn(name="PERMISSION_ID")})
	public Set<Permission> getPermissions() {
		return permissions;
	}
	
	
	@JsonIgnore
	@JSONField(serialize=false)
	@ManyToMany(fetch=FetchType.LAZY, targetEntity=User.class)
	@JoinTable(name="USER_ROLE", joinColumns =  
			@JoinColumn(name="ROLE_ID", referencedColumnName="ID"),
			inverseJoinColumns=@JoinColumn(name="USER_ID", referencedColumnName="ID"))
	public Set<User> getUsers() {
		return users;
	}

	public void setUsers(Set<User> users) {
		this.users = users;
	}

	public void setPermissions(Set<Permission> permissions) {
		this.permissions = permissions;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	@Override
	public String toString() {
		return "Role [name=" + name + ", description=" + description + ", permissions=" + permissions + ", id=" + id + "]";
	}

权限对象:

/**
 * 角色对象
 */
@Entity
@Table(name="PERMISSION")
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class Permission extends IdEntity {

	private static final long serialVersionUID = -6998691082059319752L;
	
	protected String name;//权限名称
	protected String description;//权限描述
	protected Permission parent;//所属父
	protected Set<Permission> children = new HashSet<Permission>();//拥有子权限
	protected Set<Role> roles = new HashSet<Role>();//对应角色
	protected PermissionsType type; //权限类型
	protected String url; //权限的Api地址

	@Column(name="NAME")
	public String getName() {
		return name;
	}

	@Column(name="DESCRIPTION")
	public String getDescription() {
		return description;
	}
	
	@ManyToOne(fetch=FetchType.LAZY, targetEntity=Permission.class)
	@JoinColumn(name="PARENT_ID")
	public Permission getParent() {
		return parent;
	}
	
	@OneToMany(fetch=FetchType.LAZY, targetEntity=Permission.class, cascade=CascadeType.ALL, mappedBy="parent")
	public Set<Permission> getChildren() {
		return children;
	}
	
	@Column(name="TYPE")
	@Convert(converter = PermissionsTypeConverter.class)
	public PermissionsType getType() {
		return type;
	}
	
	@JsonIgnore
	@JSONField(serialize=false)
	@ManyToMany(fetch=FetchType.LAZY, targetEntity=Role.class)
	@JoinTable(name="ROLE_PERMISSION", joinColumns =  
			@JoinColumn(name="PERMISSION_ID"),
			inverseJoinColumns=@JoinColumn(name="ROLE_ID"))
	public Set<Role> getRoles() {
		return roles;
	}

	@Column(name="URL")
	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

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

	public void setType(PermissionsType type) {
		this.type = type;
	}

	public void setParent(Permission parent) {
		this.parent = parent;
	}

	public void setChildren(Set<Permission> children) {
		this.children = children;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	@Override
	public String toString() {
		return "Permission [name=" + name + ", description=" + description + ", parent=" + parent + ", children="
				+ children + ", id=" + id + "]";
	}

}

其中权限类型主要是区分是否需要设置查询权限(根据自己的业务设置是否需要)

public enum PermissionsType {
	
	ITEM (1, "模块项"),
	
	PERMISSION (2, "单个权限");
	
	private Integer id;
	
	private String name;
	
	private PermissionsType(){
		
	}
	
	private PermissionsType(Integer id, String name){
		this.id = id;
		this.name = name;
	}

	public Integer getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public static PermissionsType getById (Integer id){
		for (PermissionsType type : PermissionsType.values()) {
			if(type.getId() == id){
				return type;
			}
		}
		return null;
	}

}

对应的数据SQL:

#用户表
DROP TABLE IF EXISTS USER;
CREATE TABLE USER (
	ID INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
	USER_NAME VARCHAR(500),
	PASSWORD VARCHAR(500),
	EMP_NAME  VARCHAR(500),
	CREATE_TIME DATETIME,
	LAST_LOGIN_TIME DATETIME
);

ALTER TABLE user ADD COLUMN PHONE VARCHAR(200);

#角色表
DROP TABLE IF EXISTS ROLE;
CREATE TABLE customize.ROLE (
	ID INT (11) NOT NULL AUTO_INCREMENT,
	NAME VARCHAR (20) NOT NULL,
	DESCRIPTION VARCHAR (500),
	PRIMARY KEY (ID)
)ENGINE = INNODB DEFAULT CHARSET = utf8;

#权限表
DROP TABLE IF EXISTS PERMISSION;
CREATE TABLE customize.PERMISSION (
	ID INT (11) NOT NULL AUTO_INCREMENT,
	NAME VARCHAR (100) NOT NULL,
	DESCRIPTION VARCHAR (500),
	PARENT_ID INT (11),
    TYPE int(1),
	PRIMARY KEY (ID)
)ENGINE = INNODB DEFAULT CHARSET = utf8;

ALTER TABLE PERMISSION ADD COLUMN URL VARCHAR(500);

#用户角色关系表
DROP TABLE IF EXISTS USER_ROLE;
CREATE TABLE customize.USER_ROLE (
	USER_ID INT (11) NOT NULL,
	ROLE_ID INT (11) NOT NULL
)ENGINE = INNODB DEFAULT CHARSET = utf8;

#角色权限关系表
DROP TABLE IF EXISTS ROLE_PERMISSION;
CREATE TABLE customize.ROLE_PERMISSION (
	ROLE_ID INT (11) NOT NULL,
	PERMISSION_ID INT (11) NOT NULL
)ENGINE = INNODB DEFAULT CHARSET = utf8;

等一切准备完毕开始建立SPRING SECURTY的配置:

MAVEN 增加依赖:

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

增加对应的FILTER主要作用是预检查全部返回200:

@Order(1)
@WebFilter(filterName = "myWebFilter", urlPatterns="/*")
public class MyWebFilter extends OncePerRequestFilter {
	
	    @Override
	    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
	    	 if("OPTIONS".equalsIgnoreCase(request.getMethod())){
		        	response.setStatus(HttpServletResponse.SC_OK);
		        	return;
		        }
	        filterChain.doFilter(request, response);
	    }
}

增加对应的跨域配置:

/**
 * 前后端分离跨域配置
 *
 */
@Component
@Order(0)
public class CorsConfig extends CorsFilter {
    public CorsConfig() {
        super(configurationSource());
    }

    private static UrlBasedCorsConfigurationSource configurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        // 设置你要允许的网站域名,如果全允许则设为 *
        config.addAllowedOrigin("http://xxx.xxx.com");
        // 如果要限制 HEADER 或 METHOD 请自行更改
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
//        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<CorsFilter>(new CorsFilter(source));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
       return source;
    }
}

增加自定义验证参数类

/**
 * 接受验证的参数 目前只有账号密码 如果有验证码需要重新加参数
 *
 */
public class MyWebAuthenticationDetails extends WebAuthenticationDetails {

	private static final long serialVersionUID = 6975601077710753878L;
    
    private String username;
    
    private String password;
    
    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;
    }


    public MyWebAuthenticationDetails(HttpServletRequest request) {
        super(request);
        username = request.getParameter("username"); 
        password = request.getParameter("password");
    }

使自定义的参数起作用:

/**
 * 配置自定义的验证参数
 */
@Component
public class MyAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {

    @Override
    public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
        return new MyWebAuthenticationDetails(context);
    }
}

WebSecurityConfig的核心配置:

@Configuration//配置文件注解
@EnableWebSecurity//禁用Boot的默认Security配置,配合@Configuration启用自定义配置(需要扩展WebSecurityConfigurerAdapter)
@EnableGlobalMethodSecurity(prePostEnabled = true)//启用Security注解,例如最常用的@PreAuthorize 本项目用的 RBAC此注解暂时不用
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Autowired
    protected MyUserDetailsService myDetailService;
    
	/**
	 * 身份验证配置,用于注入自定义身份验证Bean和密码校验规则
	 */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    	auth.userDetailsService(myDetailService).passwordEncoder(new BCryptPasswordEncoder());
    }
    
    /**
     * Request层面的配置,对应XML Configuration中的<http>元素
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
        		.httpBasic().authenticationEntryPoint(new UnauthorizedEntryPoint()) //定义出错后的异常信息
		.and()
		        .authorizeRequests()
				.antMatchers(HttpMethod.OPTIONS).permitAll()    //所有的预检查默认通过
		        .antMatchers("/user/info").permitAll()                            //登陆方法默认通过
		        .antMatchers("/user/info/").permitAll()                            //登陆方法默认通过
		        .antMatchers("/**").access("@rbacService.hasPermission(request,authentication)") //其余任何方法需要RBAC过滤URL                   
	            .anyRequest().authenticated()   //其余需要权限验证(因为RBAC过滤全局,其实此句已无意义)
        
        .and()
	            .headers()
	            .frameOptions()   //预防iframe跨域问题
	            .disable()
        .and()
                .formLogin().loginPage("http://customize.joytrav.com/login")//设置的登陆页面 默认 /login
        .and()   
        		.csrf().disable()                                          //禁用CSRF验证
        		.formLogin().successHandler(new AjaxAuthSuccessHandler())  //自定义登陆成功返回数据,否则会默认跳转成功页面
                .failureHandler(new AjaxAuthFailHandler())                 //自定义登陆失败返回数据,否则会默认跳转ERROR页面
                .loginProcessingUrl("/user/login/")                        //设置登陆的方法
        .and()
            .logout().logoutSuccessHandler(new AjaxLogoutSuccessHandler())  //自定义退出登陆返回数据,否则会默认跳转登陆页面
            .logoutUrl("/logout");                                          //自定义退出登陆URL (方法中设置销毁SESSION等逻辑)
        http.addFilterBefore(new MyWebFilter(), UsernamePasswordAuthenticationFilter.class);//将常用的WEB FILTER 在用户密码验证之前执行
    }
    
    //定义登陆成功返回信息
    private class AjaxAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpStatus.OK.value());
            PrintWriter out = response.getWriter();
            out.write("{\"status\":\"ok\",\"msg\":\"登录成功\"}");
            out.flush();
            out.close();
        }
    }
    
    //定义登陆失败返回信息
    private class AjaxAuthFailHandler extends SimpleUrlAuthenticationFailureHandler {
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            PrintWriter out = response.getWriter();
            out.write("{\"status\":\"error\",\"msg\":\"请检查用户名、密码或验证码是否正确\"}");
            out.flush();
            out.close();
        }
    }
    
    //定义异常返回信息
    public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            response.sendError(HttpStatus.FORBIDDEN.value(), authException.getMessage());
        }
    }
    
    //定义登出成功返回信息
    private class AjaxLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler  {

        public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
                Authentication authentication) throws IOException, ServletException {
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpStatus.OK.value());
            PrintWriter out = response.getWriter();
            out.write("{\"status\":\"ok\",\"msg\":\"登出成功\"}");
            out.flush();
            out.close();
        }
    }
}

增加对应的RBAC的接口:

public interface RbacService {
	
	 boolean hasPermission(HttpServletRequest request, Authentication authentication);
}

增加RBAC的实现类:

/**
 * 过滤所有的URL 如果是ADMIN 则通过权限验证,否则根据 USER.ROLES.PERMIMMIOS中皮质拥有权限的URL和当前请求的URL做对接,相等则通过,否则403
 */
@Component("rbacService")
public class RbacServiceImpl implements RbacService {
	
	@Autowired
	protected PermissionService permissionService;
	
    private AntPathMatcher antPathMatcher = new AntPathMatcher();
    
    public static Logger log = LoggerFactory.getLogger(RbacServiceImpl.class);
    
    @Override
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
    	log.debug("=========hasPermission Authentication");
        Object principe = authentication.getPrincipal();
        if(principe instanceof UserDetails) {
            //拿到用户名后可以拿到用户角色和用户所有的权限
            Collection<? extends GrantedAuthority> grantedAuthorities = ((UserDetails) principe).getAuthorities();
            //如果角色是admin 则直接通行
            for(GrantedAuthority grantedAuthority : grantedAuthorities){
            	if(grantedAuthority.getAuthority().equalsIgnoreCase("ADMIN")){
            		log.debug("hasPermission with isAdmin");
            		return true;
            	}
            }
            
            //读取用户所有的url
            Set<String> urls = new HashSet<String>();
            for(GrantedAuthority grantedAuthority : grantedAuthorities){
            	String roleName = grantedAuthority.getAuthority();
            	urls.addAll(permissionService.getPermissionUrlsByRoleName(roleName));
            }
            urls.removeAll(java.util.Collections.singleton(null));
            for(String url : urls) {
                if(antPathMatcher.match(url, request.getRequestURI())) {
                   log.debug("hasPermission with matchUrl" + request.getRequestURI());
                   return true;
                }
            }
        }
        log.debug("no Permission " + request.getRequestURI());
        return false;
    }
}

增加对应的查询逻辑,因为比较简单,此处只写一个作为示例:

@Repository
public interface PermissionDao extends BaseJpaRepository<Permission, Integer> {


    @Query("from Permission p left join p.roles roles where roles.name = :roleName") //不加nativeQuery应使用HQL
	List<Permission> getPermissionsByRoleName(@Param("roleName") String roleName);
    
    @Query("select p.url from Permission p left join p.roles roles where roles.name = :roleName") //不加nativeQuery应使用HQL
   	List<String> getPermissionUrlsByRoleName(@Param("roleName") String roleName);
	
}

对应的VUE调用 最核心的问题 就是一定用 FORM提交,否则永远返回自带的登录页面:

 this.$axios.post(
                            '/user/login/', 
                            fd, 
                            config
                        )
                    .then(res => {
                        //请求成功后的处理函数  
                        console.log("res.data" + JSON.stringify(res.data));
                        if(res.data != null && res.data != ""){
                            this.logining = false;
                            //如果返回成功则获取userInfo
                            this.$axios.post(
                                '/user/info/'
                            )
                            .then(res => {
                                sessionStorage.setItem("auth-user", JSON.stringify(res.data));
                                console.log(" this.$router" +  this.$router);
                                this.$router.push({path: '/index'});
                            }).catch(err => {//请求失败后的处理函数   
                                this.$alert('报错了'+err);
                                console.log(err);
                                this.logining = false;
                            });
                        } else {
                            this.$alert('账号或密码错误', '提示', {
                            confirmButtonText: 'ok'
                        })}
                        this.logining = false;
                    }).catch(err => {                //请求失败后的处理函数   
                        this.$alert('报错了'+err);
                        console.log(err);
                        this.logining = false;
                    });

 

对应的APACHE配置 可以根据实际情况配置  NGINX 等:

<VirtualHost *:80>
	ServerName api.xxxx.com
	Header set Access-Control-Allow-Origin http://xxx.xxx.com
	Header set Access-Control-Allow-Credentials true
	Header set Access-Control-Allow-Headers content-type

    ProxyRequests Off
    ProxyPreserveHost On
	
	ProxyPass / http://127.0.0.1:7889/
	ProxyPassReverse / http://127.0.0.1:7889/
</VirtualHost>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章