Spring Security 是为基于 Spring 的应用程序提供声明式安全保护的安全性框架,基于 Spring AOP 和 Servlet 过滤器实现,能够在 Web 请求级别和方法调用级别处理身份验证和授权。它使用 Servlet 过滤器保护 Web 请求和限制 URL 级别的访问,也可以使用 Spring AOP 保护方法调用——借助于对象代理和使用通知,能够确保只有具备适当权限的用户才能访问安全保护的方法。

web.xml

1
2
3
4
5
6
7
8
9
<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>

security.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/security
           http://www.springframework.org/schema/security/spring-security.xsd">

    <global-method-security pre-post-annotations="enabled"/>

    <http auto-config="false" use-expressions="true">
        <intercept-url pattern="/u**" access="hasRole('ROLE_USER')"/>
        <intercept-url pattern="/u/**" access="hasRole('ROLE_USER')"/>
        <intercept-url pattern="/admin**" access="hasRole('ROLE_ADMIN')"/>
        <form-login login-page="/index" default-target-url="/userhome" authentication-failure-url="/index?error=true"/>
        <logout logout-url="/logout"/>
    </http>

    <authentication-manager>
        <authentication-provider>
            <password-encoder hash="bcrypt"/>

            <jdbc-user-service data-source-ref="dataSource"
                authorities-by-username-query="select user_tbl.user_name, role_tbl.name from user_tbl
										join user_tbl_role_tbl on user_tbl.user_id=user_tbl_role_tbl.users_user_id
										join role_tbl on user_tbl_role_tbl.roles_role_id = role_tbl.role_id
										where user_tbl.user_name = ?"
                users-by-username-query="select user_name, password, true from user_tbl where user_name = ?" />

                <!--<user-service>-->
                    <!--<user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" />-->
                <!--</user-service>-->
        </authentication-provider>
    </authentication-manager>

</beans:beans>

User.java, Role.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Entity
@Table(name="user_tbl")
public class User implements Serializable{
    private static final long serialVersionUID = 1L;

    @Id
    @Column(name="user_id")
    @GeneratedValue
    private Long userId;

    @Column(name="user_name")
    @Size(min=3)
    private String userName;

    private String fullname;

    @Size(min=1)
    @Email
    private String email;

    @Size(min=3)
    @Column(name="password")
    private String password;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable
    private List<Role> roles;
}

@Entity
@Table(name="role_tbl")
public class Role {

    @Id
    @Column(name="role_id")
    @GeneratedValue
    private Long userId;

    private String name;

    @ManyToMany(mappedBy = "roles")
    private List<User> users;
}

HomeController.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Controller
public class HomeController {
    static final Logger logger = (Logger) LoggerFactory.getLogger(HomeController.class);

    @Autowired
    private HibUserService hibUserService;

    @Autowired
    private HibSpittleService hibSpittleService;

    @RequestMapping(value="index")
    public String login(Model model) {
        List<Spittle> spittleList = hibSpittleService.getRecentSpittles(5);
        model.addAttribute("spittleList", spittleList);

        return "index";
    }

    @RequestMapping(value="userhome")
    public String loginSuccess(HttpServletRequest request, Principal principal) {
        String username = principal.getName();
        User user = hibUserService.findUserByName(username);
        user.setLastIp(request.getRemoteAddr());
        user.setLastVisit(new java.sql.Timestamp((new java.util.Date()).getTime()));
        hibUserService.loginSuccess(user);
        request.getSession().setAttribute("currentUser", user);

        logger.info("User id: " + user.getUserId().toString());

        return "redirect:u/" + username;
    }
}

现在不登录访问 /u, /admin url 路径,都会跳到首页。

实现一个 Spittle 只有管理员和其作者才可以删除,在 Service 类上应用方法级安全措施

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Service
public class HibSpittleService {
    @Autowired
    private HibSpittleDao hibSpittleDao;
    //...

    @Transactional
    @PreAuthorize("#spittle.user.userName == authentication.name or hasRole('ROLE_ADMIN')")
    public void delete(@P("spittle") Spittle spittle) {
        hibSpittleDao.delSpittle(spittle);
    }
}

在与 AJAX 技术结合时,如果出现 Could not write JSON: failed to lazily initialize a collection of role,参考

https://github.com/FasterXML/jackson-datatype-hibernate

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module;

public class HibernateAwareObjectMapper extends ObjectMapper {

    public HibernateAwareObjectMapper() {
        Hibernate4Module hm = new Hibernate4Module();
        hm.configure(Hibernate4Module.Feature.FORCE_LAZY_LOADING, false);
        registerModule(hm);
        configure(SerializationFeature.INDENT_OUTPUT, true);
    }

}