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>

 

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