spring mvc集成spring security案例

文章首发 http://surpass.esy.es

代码结构

总共涉及到7个类文件和添加角色配置文件以及需要在web.xml中添加一个过滤器(如果是spring boot的话则不需要配置web.xml)

代码结构

涉及文件及其作用简单分析

  1. role-config.properties:用来配置所有角色及所持权限信息
  2. Role.java:将配置文件中的权限信息分装到java类中,方便处理
  3. RoleHandler.java:构建Role对象列表,以便可以通过配置文件的key获取到相关Role对象
  4. 'UserDtailsBean.java':根绝具体业务需求来给用户对象添加(自定义)security需要的属性
  5. 'UserDetailsServiceCustom':自定义用户信息获取服务,主要是重写loadUserByUsername方法
  6. AuthenticationProviderCustom.java:自定义授权认证类,当用户登录,则会调用该方法,授权操作就是在这里进行的
  7. CustomSuccessHandler:当登录成功后的处理操作,可以省去或直接跳转到某个页面
  8. SecurityConfig:Security的核心配置类,在这里使用了自定义的AuthenticationProviderCustom和AuthenticationProviderCustom进行用户信息的构建以及授权类,具体配置内容可参考官网(当然,也可以用XML进行配置,而不是Java Config的形式)
  9. web.xml:配置security过滤器(使用spring boot的话无须配置)

具体代码案例

1.在配置文件中配置好各个角色的信息,security会默认解析以ROLE_开头的权限内容作为角色,例如ROLE_ADMIN,则将作为ADMIN角色

角色配置

2.添加角色Bean对象

import java.util.List;

public class Role {
    private String name;    //  名称
    private List<String> privileges;    //  分配的角色/权限

    Role(){

    }

    Role(String name, List<String> privileges){
        this.name = name;
        this.privileges = privileges;
    }

    List<String> getPrivileges(){
        return this.privileges;
    }

    String getName() {
        return name;
    }

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

3.提供角色权限初始化类

import com.x.x.common.UserType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * 角色权限管理以及初始化
 * 目前是读取配置文件来初始化用户权限
 * <p>
 * Created by surpass.wei@gmail.com on 2017/3/6.
 */
@Component
public class RoleHandler {

    @Value("${role.admin}")
    private String adminRoleStr;

    @Value("${role.teacher}")
    private String teacherRoleStr;

    @Value("${role.trainee}")
    private String traineeRoleStr;

    private final List<Role> roleList = new ArrayList<>();

    /**
     * 根据config.properties配置文件中的角色权限控制来初始化roles
     */
    private void init() {
        UserType[] userTypes = UserType.values();

        Role role;
        List<String> privilegeList;
        /**
         * 循环角色类型获取相应的配置字符串,根据“,”分隔
         * 构建Role的List
         */
        for (UserType userType : userTypes) {

            String currentRoleStr;  //  当前角色的权限信息
            if (userType.equals(UserType.ADMIN)) {
                currentRoleStr = adminRoleStr;
            } else if (userType.equals(UserType.TEACHER)) {
                currentRoleStr = teacherRoleStr;
            } else if (userType.equals(UserType.TRAINEE)) {
                currentRoleStr = traineeRoleStr;
            } else {
                continue;
            }
            if (currentRoleStr.isEmpty()) {
                return;
            }

            privilegeList = new ArrayList<>();
            String[] roleArr = currentRoleStr.split(",");
            for (String s : roleArr) {
                privilegeList.add(s.trim());
            }
            role = new Role(userType.getRoleName(), privilegeList);

            roleList.add(role);
        }

    }

    public Role getRole(String roleName) {
        if (roleList.isEmpty()) {
            this.init();
        }

        if (roleName == null || roleName.isEmpty()) {
            return null;
        }

        Role role = new Role();
        for (Role temp_role : roleList) {
            if (temp_role.getName().compareToIgnoreCase(roleName) == 0) {
                role = temp_role;
            }
        }

        return role;
    }
}

4.封装UserDetailBean

import com.x.x.common.UserType;
import com.x.x.entity.User;
import com.x.x.util.BeanUtil;
import com.x.x.util.SpringContextUtil;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;

/**
 * 基于User包装一个UserDetailsBean(用于security权限认证)
 * 主要为了实现 getAuthorities()
 */
public class UserDetailsBean extends User implements UserDetails {

    public UserDetailsBean(User user) {
        BeanUtil.copyObjValue(user, this);
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //  获取用户的角色/权限信息

        Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
        RoleHandler roleHandler = SpringContextUtil.getApplicationContext().getBean(RoleHandler.class);

        if (roleHandler == null) {
            return null;
        }

        // 根据用户的类型, 构建用户对应的Role
        String roleName;

        roleName = UserType.getRoleName(this.getUserType());
        Role role = roleHandler.getRole(roleName);

        //  循环角色的权限信息, 构建authorities
        for (String privilege : role.getPrivileges()) {
            authorities.add(new SimpleGrantedAuthority(privilege));
        }

        return authorities;
    }

    @Override
    public String getPassword() {
        return this.getPwd();
    }

    @Override
    public String getUsername() {
        return this.getLoginName();
    }

    @Override
    public boolean isAccountNonExpired() {
        //  账户是否未过期
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        //  账户是否未锁定
        return this.getUserState() == 0;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        // 凭证是否未过期
        return true;
    }

    @Override
    public boolean isEnabled() {
        //  是否有效
        return true;
    }
}

5.实现UserDetailsService重写loadUserByUsername方法

import com.x.x.entity.User;
import com.x.x.service.IUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.Component;

import javax.annotation.Resource;

/**
 * 重写loadUserByUsername
 * Created by surpass.wei@gmail.com on 2016/12/16.
 */
@Component
public class UserDetailsServiceCustom implements UserDetailsService {
    private Logger logger = LoggerFactory.getLogger(UserDetailsServiceCustom.class);

    @Resource
    private IUserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        UserDetailsBean userDetailsBean;

        User search = new User();
        search.setLoginName(username);
        User user = userService.findOneByEntity(search);

        if (user==null) {
            logger.warn("该用户不存在:" + username);
            throw new UsernameNotFoundException("该用户不存在:" + username);
        } else {
            userDetailsBean = new UserDetailsBean(user);
        }

        return userDetailsBean;
    }
}

6.自定义的授权认证类

import com.x.x.entity.User;
import com.x.x.service.IUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 javax.annotation.Resource;

@Component
public class AuthenticationProviderCustom implements AuthenticationProvider {
    private Logger logger = LoggerFactory.getLogger(AuthenticationProviderCustom.class);

    @Resource
    private IUserService userService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
        String loginName = token.getName();
        String password = token.getCredentials().toString();
        User user = userService.getUserByLoginNameAndPwd(loginName, password);

        UserDetailsBean userDetailsBean;
        if(user != null) {
            userDetailsBean = new UserDetailsBean(user);
        } else {
            logger.error("用户名或密码错误");
            throw new BadCredentialsException("用户名或密码错误");
        }

        return new UsernamePasswordAuthenticationToken(userDetailsBean, password, userDetailsBean.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

7.配置Security

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;

import javax.annotation.Resource;


@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity //这里如果放开js文件将不被允许执行,注释掉也没有影响,不知道为什么
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private CustomSuccessHandler customSuccessHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/**").permitAll()
                //.antMatchers("/static/**").permitAll()  //  允许所有用户对根路径以及匹配"/static/"开头的路径的访问
                //.antMatchers("/", "/index").hasRole("TEACHER")
                .anyRequest().authenticated()   //  任何尚未匹配的的URL地址只需要对用户进行权限验证
                .and()
                .formLogin()
                    .successHandler(customSuccessHandler)
                    .failureUrl("/login?error=true")
                    //.defaultSuccessUrl("/home")
                    //.defaultSuccessUrl("/swagger-ui.html")  //  登陆成功后默认默认跳转到swagger页
                    .permitAll()
                    .and()
                    .rememberMe().tokenValiditySeconds(604800);
        http.logout();
        http.csrf().disable();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        //  将验证过程交给自定义的授权认证类
        auth.authenticationProvider(authenticationProvider());
    }

    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        return new UserDetailsServiceCustom();
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        return new AuthenticationProviderCustom();
    }
}

8.自定义成功处理类(可选)

import org.springframework.security.core.Authentication;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


@Component
public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    protected void handle(HttpServletRequest request, HttpServletResponse response,
                          Authentication authentication) throws IOException, ServletException {
        UserDetailsBean user = (UserDetailsBean) authentication.getPrincipal();

        String targetUrl;

        if (UserType.ADMIN.getValue().equals(user.getUserType())) {
            targetUrl = "/swagger-ui.html";
        } else if (UserType.TEACHER.getValue().equals(user.getUserType())) {
            targetUrl = "/teacher";
        } else if (UserType.TRAINEE.getValue().equals(user.getUserType())){
            targetUrl = "/trainee";
        } else {
            targetUrl = "/login?error";
        }

        redirectStrategy.sendRedirect(request, response, targetUrl);
    }
}

9.添加security过滤器

  <!--spring security-->
  <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容